From 999a61bc9ed48e9797330fe238721bc936ec69fb Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Sat, 11 Apr 2026 13:41:01 -0700 Subject: [PATCH] Chore: add generic type params to view classes Add model type parameters to ModelViewSet, GenericViewSet, ReadOnlyModelViewSet, and Any to GenericAPIView subclasses in documents/views.py, paperless/views.py, and paperless_mail/views.py. Also reorder mixin/ModelViewSet base class order to mixin-first convention in ShareLink* and MailAccount/MailRule viewsets. Removes 26 [type-arg] errors from the mypy baseline. Co-Authored-By: Claude Sonnet 4.6 --- .mypy-baseline.txt | 26 ------------------- .pyrefly-baseline.json | 16 ++++++------ src/documents/views.py | 50 +++++++++++++++++++++---------------- src/paperless_mail/views.py | 9 ++++--- 4 files changed, 41 insertions(+), 60 deletions(-) diff --git a/.mypy-baseline.txt b/.mypy-baseline.txt index 1442a4813..2a560ebf1 100644 --- a/.mypy-baseline.txt +++ b/.mypy-baseline.txt @@ -1805,28 +1805,6 @@ src/documents/views.py:0: error: Item "None" of "Any | None" has no attribute "g src/documents/views.py:0: error: Item "None" of "ApplicationConfiguration | None" has no attribute "app_logo" [union-attr] src/documents/views.py:0: error: Item "None" of "dict[str, _PingReply] | None" has no attribute "keys" [union-attr] src/documents/views.py:0: error: Missing positional argument "request" in call to "email_documents" [call-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "GenericViewSet" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -src/documents/views.py:0: error: Missing type arguments for generic type "ReadOnlyModelViewSet" [type-arg] src/documents/views.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] src/documents/views.py:0: error: Missing type arguments for generic type "list" [type-arg] src/documents/views.py:0: error: Need type annotation for "authentication_classes" (hint: "authentication_classes: list[] = ...") [var-annotated] @@ -2629,7 +2607,3 @@ src/paperless_mail/views.py:0: error: Function is missing a type annotation [no src/paperless_mail/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/paperless_mail/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/paperless_mail/views.py:0: error: Incompatible types in assignment (expression has type "tuple[type[IsAuthenticated]]", variable has type "tuple[type[IsAuthenticated], type[PaperlessObjectPermissions]]") [assignment] -src/paperless_mail/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg] -src/paperless_mail/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -src/paperless_mail/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -src/paperless_mail/views.py:0: error: Missing type arguments for generic type "ReadOnlyModelViewSet" [type-arg] diff --git a/.pyrefly-baseline.json b/.pyrefly-baseline.json index f3f6a0a5b..5013e0983 100644 --- a/.pyrefly-baseline.json +++ b/.pyrefly-baseline.json @@ -11048,8 +11048,8 @@ "path": "src/documents/views.py", "code": -2, "name": "bad-argument-type", - "description": "Argument `TantivyRelevanceList` is not assignable to parameter `queryset` with type `QuerySet[Unknown, @_]` in function `rest_framework.generics.GenericAPIView.paginate_queryset`", - "concise_description": "Argument `TantivyRelevanceList` is not assignable to parameter `queryset` with type `QuerySet[Unknown, @_]` in function `rest_framework.generics.GenericAPIView.paginate_queryset`", + "description": "Argument `TantivyRelevanceList` is not assignable to parameter `queryset` with type `QuerySet[Any, @_]` in function `rest_framework.generics.GenericAPIView.paginate_queryset`", + "concise_description": "Argument `TantivyRelevanceList` is not assignable to parameter `queryset` with type `QuerySet[Any, @_]` in function `rest_framework.generics.GenericAPIView.paginate_queryset`", "severity": "error" }, { @@ -16009,9 +16009,9 @@ "severity": "error" }, { - "line": 267, + "line": 268, "column": 28, - "stop_line": 267, + "stop_line": 268, "stop_column": 34, "path": "src/paperless_mail/views.py", "code": -2, @@ -16021,9 +16021,9 @@ "severity": "error" }, { - "line": 273, + "line": 274, "column": 29, - "stop_line": 273, + "stop_line": 274, "stop_column": 40, "path": "src/paperless_mail/views.py", "code": -2, @@ -16033,9 +16033,9 @@ "severity": "error" }, { - "line": 277, + "line": 278, "column": 26, - "stop_line": 277, + "stop_line": 278, "stop_column": 34, "path": "src/paperless_mail/views.py", "code": -2, diff --git a/src/documents/views.py b/src/documents/views.py index dd9c4b837..7c20e1cda 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -291,7 +291,7 @@ class IndexView(TemplateView): return context -class PassUserMixin(GenericAPIView): +class PassUserMixin(GenericAPIView[Any]): """ Pass a user object to serializer """ @@ -457,7 +457,10 @@ class PermissionsAwareDocumentCountMixin(BulkPermissionMixin, PassUserMixin): @extend_schema_view(**generate_object_with_permissions_schema(CorrespondentSerializer)) -class CorrespondentViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet): +class CorrespondentViewSet( + PermissionsAwareDocumentCountMixin, + ModelViewSet[Correspondent], +): model = Correspondent queryset = Correspondent.objects.select_related("owner").order_by(Lower("name")) @@ -494,7 +497,7 @@ class CorrespondentViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet): @extend_schema_view(**generate_object_with_permissions_schema(TagSerializer)) -class TagViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet): +class TagViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet[Tag]): model = Tag serializer_class = TagSerializer document_count_through = Document.tags.through @@ -573,7 +576,10 @@ class TagViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet): @extend_schema_view(**generate_object_with_permissions_schema(DocumentTypeSerializer)) -class DocumentTypeViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet): +class DocumentTypeViewSet( + PermissionsAwareDocumentCountMixin, + ModelViewSet[DocumentType], +): model = DocumentType queryset = DocumentType.objects.select_related("owner").order_by(Lower("name")) @@ -808,7 +814,7 @@ class DocumentViewSet( UpdateModelMixin, DestroyModelMixin, ListModelMixin, - GenericViewSet, + GenericViewSet[Document], ): model = Document queryset = Document.objects.all() @@ -1952,7 +1958,7 @@ class ChatStreamingSerializer(serializers.Serializer): ], name="dispatch", ) -class ChatStreamingView(GenericAPIView): +class ChatStreamingView(GenericAPIView[Any]): permission_classes = (IsAuthenticated,) serializer_class = ChatStreamingSerializer @@ -2278,7 +2284,7 @@ class LogViewSet(ViewSet): @extend_schema_view(**generate_object_with_permissions_schema(SavedViewSerializer)) -class SavedViewViewSet(BulkPermissionMixin, PassUserMixin, ModelViewSet): +class SavedViewViewSet(BulkPermissionMixin, PassUserMixin, ModelViewSet[SavedView]): model = SavedView queryset = SavedView.objects.select_related("owner").prefetch_related( @@ -2756,7 +2762,7 @@ class RemovePasswordDocumentsView(DocumentOperationPermissionMixin): }, ), ) -class PostDocumentView(GenericAPIView): +class PostDocumentView(GenericAPIView[Any]): permission_classes = (IsAuthenticated,) serializer_class = PostDocumentSerializer parser_classes = (parsers.MultiPartParser,) @@ -2877,7 +2883,7 @@ class PostDocumentView(GenericAPIView): }, ), ) -class SelectionDataView(GenericAPIView): +class SelectionDataView(GenericAPIView[Any]): permission_classes = (IsAuthenticated,) serializer_class = DocumentListSerializer parser_classes = (parsers.MultiPartParser, parsers.JSONParser) @@ -2981,7 +2987,7 @@ class SelectionDataView(GenericAPIView): }, ), ) -class SearchAutoCompleteView(GenericAPIView): +class SearchAutoCompleteView(GenericAPIView[Any]): permission_classes = (IsAuthenticated,) def get(self, request, format=None): @@ -3262,7 +3268,7 @@ class GlobalSearchView(PassUserMixin): }, ), ) -class StatisticsView(GenericAPIView): +class StatisticsView(GenericAPIView[Any]): permission_classes = (IsAuthenticated,) def get(self, request, format=None): @@ -3364,7 +3370,7 @@ class StatisticsView(GenericAPIView): ) -class BulkDownloadView(DocumentSelectionMixin, GenericAPIView): +class BulkDownloadView(DocumentSelectionMixin, GenericAPIView[Any]): permission_classes = (IsAuthenticated,) serializer_class = BulkDownloadSerializer parser_classes = (parsers.JSONParser,) @@ -3417,7 +3423,7 @@ class BulkDownloadView(DocumentSelectionMixin, GenericAPIView): @extend_schema_view(**generate_object_with_permissions_schema(StoragePathSerializer)) -class StoragePathViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet): +class StoragePathViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet[StoragePath]): model = StoragePath queryset = StoragePath.objects.select_related("owner").order_by( @@ -3481,7 +3487,7 @@ class StoragePathViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet): return Response(result) -class UiSettingsView(GenericAPIView): +class UiSettingsView(GenericAPIView[Any]): queryset = UiSettings.objects.all() permission_classes = (IsAuthenticated, PaperlessObjectPermissions) serializer_class = UiSettingsViewSerializer @@ -3579,7 +3585,7 @@ class UiSettingsView(GenericAPIView): }, ), ) -class RemoteVersionView(GenericAPIView): +class RemoteVersionView(GenericAPIView[Any]): cache_key = "remote_version_view_latest_release" def get(self, request, format=None): @@ -3656,7 +3662,7 @@ class RemoteVersionView(GenericAPIView): ), ], ) -class TasksViewSet(ReadOnlyModelViewSet): +class TasksViewSet(ReadOnlyModelViewSet[PaperlessTask]): permission_classes = (IsAuthenticated, PaperlessObjectPermissions) serializer_class = TasksViewSerializer filter_backends = ( @@ -3730,7 +3736,7 @@ class TasksViewSet(ReadOnlyModelViewSet): ) -class ShareLinkViewSet(ModelViewSet, PassUserMixin): +class ShareLinkViewSet(PassUserMixin, ModelViewSet[ShareLink]): model = ShareLink queryset = ShareLink.objects.all() @@ -3747,7 +3753,7 @@ class ShareLinkViewSet(ModelViewSet, PassUserMixin): ordering_fields = ("created", "expiration", "document") -class ShareLinkBundleViewSet(ModelViewSet, PassUserMixin): +class ShareLinkBundleViewSet(PassUserMixin, ModelViewSet[ShareLinkBundle]): model = ShareLinkBundle queryset = ShareLinkBundle.objects.all() @@ -4104,7 +4110,7 @@ class BulkEditObjectsView(PassUserMixin): return Response({"result": "OK"}) -class WorkflowTriggerViewSet(ModelViewSet): +class WorkflowTriggerViewSet(ModelViewSet[WorkflowTrigger]): permission_classes = (IsAuthenticated, PaperlessObjectPermissions) serializer_class = WorkflowTriggerSerializer @@ -4122,7 +4128,7 @@ class WorkflowTriggerViewSet(ModelViewSet): return super().partial_update(request, *args, **kwargs) -class WorkflowActionViewSet(ModelViewSet): +class WorkflowActionViewSet(ModelViewSet[WorkflowAction]): permission_classes = (IsAuthenticated, PaperlessObjectPermissions) serializer_class = WorkflowActionSerializer @@ -4147,7 +4153,7 @@ class WorkflowActionViewSet(ModelViewSet): return super().partial_update(request, *args, **kwargs) -class WorkflowViewSet(ModelViewSet): +class WorkflowViewSet(ModelViewSet[Workflow]): permission_classes = (IsAuthenticated, PaperlessObjectPermissions) serializer_class = WorkflowSerializer @@ -4165,7 +4171,7 @@ class WorkflowViewSet(ModelViewSet): ) -class CustomFieldViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet): +class CustomFieldViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet[CustomField]): permission_classes = (IsAuthenticated, PaperlessObjectPermissions) serializer_class = CustomFieldSerializer diff --git a/src/paperless_mail/views.py b/src/paperless_mail/views.py index 9e3850cfc..c4ec2da40 100644 --- a/src/paperless_mail/views.py +++ b/src/paperless_mail/views.py @@ -1,6 +1,7 @@ import datetime import logging from datetime import timedelta +from typing import Any from django.http import HttpResponseBadRequest from django.http import HttpResponseForbidden @@ -65,7 +66,7 @@ from paperless_mail.tasks import process_mail_accounts }, ), ) -class MailAccountViewSet(ModelViewSet, PassUserMixin): +class MailAccountViewSet(PassUserMixin, ModelViewSet[MailAccount]): model = MailAccount queryset = MailAccount.objects.all().order_by("pk") @@ -159,7 +160,7 @@ class MailAccountViewSet(ModelViewSet, PassUserMixin): return Response({"result": "OK"}) -class ProcessedMailViewSet(ReadOnlyModelViewSet, PassUserMixin): +class ProcessedMailViewSet(PassUserMixin, ReadOnlyModelViewSet[ProcessedMail]): permission_classes = (IsAuthenticated, PaperlessObjectPermissions) serializer_class = ProcessedMailSerializer pagination_class = StandardPagination @@ -187,7 +188,7 @@ class ProcessedMailViewSet(ReadOnlyModelViewSet, PassUserMixin): return Response({"result": "OK", "deleted_mail_ids": mail_ids}) -class MailRuleViewSet(ModelViewSet, PassUserMixin): +class MailRuleViewSet(PassUserMixin, ModelViewSet[MailRule]): model = MailRule queryset = MailRule.objects.all().order_by("order") @@ -203,7 +204,7 @@ class MailRuleViewSet(ModelViewSet, PassUserMixin): responses={200: None}, ), ) -class OauthCallbackView(GenericAPIView): +class OauthCallbackView(GenericAPIView[Any]): permission_classes = (IsAuthenticated,) def get(self, request, format=None):