Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions codex/serializers/fields/vuetify.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
class VuetifyNullCodeFieldMixin:
"""Convert Vuetify null codes to None."""

NULL_CODE: int = VUETIFY_NULL_CODE
# Compared against the parsed value, so char fields set it as a str.
NULL_CODE: int | str = VUETIFY_NULL_CODE

def to_internal_value(self, data):
"""Convert numeric null code to None."""
Expand Down Expand Up @@ -57,7 +58,7 @@ class VuetifyIntegerField(VuetifyNullCodeFieldMixin, IntegerField): # pyright:
class VuetifyCharField(VuetifyNullCodeFieldMixin, CharField): # pyright: ignore[reportIncompatibleMethodOverride], # ty: ignore[invalid-method-override]
"""Char Field with null code conversion."""

NULL_CODE: str = str(VUETIFY_NULL_CODE) # pyright: ignore[reportIncompatibleVariableOverride]
NULL_CODE = str(VUETIFY_NULL_CODE)


class VuetifyBooleanField(VuetifyNullCodeFieldMixin, BooleanField): # pyright: ignore[reportIncompatibleMethodOverride], # ty: ignore[invalid-method-override]
Expand Down
5 changes: 2 additions & 3 deletions codex/views/admin/tagwrite.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
"""Admin Tag Write View."""

import json
from collections.abc import Sequence
from functools import cached_property
from pathlib import Path
from types import MappingProxyType
from typing import override

from rest_framework.permissions import BasePermission, IsAdminUser
from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response
from rest_framework.status import HTTP_202_ACCEPTED

Expand Down Expand Up @@ -43,7 +42,7 @@ class FilteredComicPksView(BrowserFilterView):
:class:`~codex.views.browser.force_update.ForceUpdateView`.
"""

permission_classes: Sequence[type[BasePermission]] = (IsAdminUser,)
permission_classes = (IsAdminUser,)
TARGET: str = "force_update" # recursive folder rel + filter semantics

def __init__(self, *args, **kwargs) -> None:
Expand Down
12 changes: 8 additions & 4 deletions codex/views/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework.exceptions import NotAuthenticated
from rest_framework.generics import GenericAPIView
from rest_framework.permissions import BasePermission, IsAuthenticated
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

Expand All @@ -72,6 +72,8 @@
from codex.views.envelope import EnvelopeJSONRenderer as _EnvelopeJSONRenderer

if TYPE_CHECKING:
# _PermissionClass exists only in djangorestframework-stubs, not at runtime.
from rest_framework.permissions import _PermissionClass
from rest_framework.request import Request


Expand Down Expand Up @@ -111,7 +113,9 @@ class AuthMixin:
# of the GET dict when the URL pattern doesn't carry it.
requires_page: bool = False

permission_classes: Sequence[type[BasePermission]] = (
# DRF's own type, so subclasses that set their own classes override
# APIView's declaration and this one alike.
permission_classes: Sequence["_PermissionClass"] = (
IsAuthenticatedOrEnabledNonUsers,
)
renderer_classes = (_EnvelopeJSONRenderer,)
Expand Down Expand Up @@ -177,11 +181,11 @@ def _ensure_session_key(self) -> str | None:
return session.session_key


class AuthAPIView(AuthMixin, APIView): # pyright: ignore[reportIncompatibleVariableOverride]
class AuthAPIView(AuthMixin, APIView):
"""Auth Policy APIView."""


class AuthGenericAPIView(AuthMixin, GenericAPIView): # pyright: ignore[reportIncompatibleVariableOverride]
class AuthGenericAPIView(AuthMixin, GenericAPIView):
"""Auth Policy GenericAPIView."""


Expand Down
4 changes: 1 addition & 3 deletions codex/views/browser/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,7 @@
class BrowserChoicesViewBase(BrowserFilterView):
"""Get choices for filter dialog."""

input_serializer_class: type[BrowserFilterChoicesInputSerializer] = ( # pyright: ignore[reportIncompatibleVariableOverride]
BrowserFilterChoicesInputSerializer
)
input_serializer_class = BrowserFilterChoicesInputSerializer
TARGET: str = "choices"

@staticmethod
Expand Down
5 changes: 2 additions & 3 deletions codex/views/browser/force_update.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"""Force update browser view: re-import metadata for a filtered comic set."""

from collections.abc import Sequence
from functools import cached_property
from types import MappingProxyType
from typing import override

from drf_spectacular.utils import extend_schema
from rest_framework.permissions import BasePermission, IsAdminUser
from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response

from codex.librarian.mp_queue import LIBRARIAN_QUEUE
Expand All @@ -19,7 +18,7 @@
class ForceUpdateView(BrowserFilterView):
"""Force a metadata re-import for every comic under a collection + filters."""

permission_classes: Sequence[type[BasePermission]] = (IsAdminUser,)
permission_classes = (IsAdminUser,)
serializer_class = OKSerializer
TARGET: str = "force_update"

Expand Down
4 changes: 1 addition & 3 deletions codex/views/browser/metadata/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ class MetadataView(MetadataCopyIntersectionsView):
"""Aggregate Collection and Comic Metadata View."""

serializer_class: type[BaseSerializer] | None = MetadataSerializer
input_serializer_class: type[BrowserFilterChoicesInputSerializer] = ( # pyright: ignore[reportIncompatibleVariableOverride]
BrowserFilterChoicesInputSerializer
)
input_serializer_class = BrowserFilterChoicesInputSerializer
TARGET: str = "metadata"
ADMIN_FLAGS = (AdminFlagChoices.FOLDER_VIEW,)

Expand Down
4 changes: 1 addition & 3 deletions codex/views/browser/mtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@
class MtimeView(BrowserCollectionMtimeView):
"""Get the mtimes for the submitted collections."""

input_serializer_class: type[CollectionsMtimeSerializer] = ( # pyright: ignore[reportIncompatibleVariableOverride]
CollectionsMtimeSerializer
)
input_serializer_class = CollectionsMtimeSerializer
serializer_class: type[BaseSerializer] | None = MtimeSerializer

TARGET: str = "mtime"
Expand Down
6 changes: 4 additions & 2 deletions codex/views/browser/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@
from codex.librarian.bookmark.tasks import LastRouteUpdateTask
from codex.librarian.mp_queue import LIBRARIAN_QUEUE
from codex.serializers.browser.settings import (
BrowserFilterChoicesInputSerializer,
BrowserSettingsSerializer,
BrowserSettingsSerializerBase,
)
from codex.views.browser.settings import BrowserSettingsBaseView


class BrowserParamsView(BrowserSettingsBaseView):
"""Browser Params Parsing."""

input_serializer_class: type[BrowserSettingsSerializerBase] = (
# The common base of every input serializer a subclass sets: choices
# and metadata take filters only, mtime its own shape.
input_serializer_class: type[BrowserFilterChoicesInputSerializer] = (
BrowserSettingsSerializer
)

Expand Down
4 changes: 2 additions & 2 deletions codex/views/frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from collections.abc import Sequence

from caseconverter import camelcase
from rest_framework.permissions import AllowAny, BasePermission
from rest_framework.permissions import AllowAny
from rest_framework.renderers import BaseRenderer, TemplateHTMLRenderer
from rest_framework.response import Response

Expand All @@ -15,7 +15,7 @@
class IndexView(BrowserSettingsBaseView, UserActiveMixin):
"""The main app."""

permission_classes: Sequence[type[BasePermission]] = (AllowAny,)
permission_classes = (AllowAny,)
renderer_classes: Sequence[type[BaseRenderer]] = [TemplateHTMLRenderer]
template_name = "index.html"

Expand Down
4 changes: 3 additions & 1 deletion codex/views/opds/binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class OPDSDownloadView(OPDSAuthMixin, DownloadView):
class OPDSPageView(OPDSAuthMixin, ReaderPageView):
"""Page View with Basic Auth."""

content_negotiation_class: type[BaseContentNegotiation] = ( # pyright:ignore[reportIncompatibleVariableOverride]
# djangorestframework-stubs types this `str | None`, but DRF resolves
# the DEFAULT_CONTENT_NEGOTIATION_CLASS import string to a class.
content_negotiation_class: type[BaseContentNegotiation] = ( # pyright: ignore[reportIncompatibleVariableOverride], # ty: ignore[invalid-attribute-override]
IgnoreClientContentNegotiation
)
2 changes: 1 addition & 1 deletion codex/views/opds/opensearch/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class _OpenSearchTemplateMixin(CodexXMLTemplateMixin):


@extend_schema(responses={("200", MimeType.OPENSEARCH): OpenApiTypes.BYTE})
class OpenSearch1View(_OpenSearchTemplateMixin, OPDSAuthMixin, CodexAPIView): # pyright: ignore[reportIncompatibleVariableOverride]
class OpenSearch1View(_OpenSearchTemplateMixin, OPDSAuthMixin, CodexAPIView):
"""OpenSearchView."""

# ``_OpenSearchTemplateMixin`` must precede ``OPDSAuthMixin`` so its
Expand Down
2 changes: 1 addition & 1 deletion codex/views/opds/v1/feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class OPDS1FeedView(OPDS1LinksView):

template_name = "opds_v1/index.xml"
serializer_class: type[BaseSerializer] | None = OPDS1TemplateSerializer
input_serializer_class: type[OPDSSettingsSerializer] = OPDSSettingsSerializer
input_serializer_class = OPDSSettingsSerializer
throttle_classes: Sequence[type[BaseThrottle]] = (ScopedRateThrottle,)
throttle_scope = "opds"
# The collection feed URLs carry no page segment; AuthMixin pulls
Expand Down
13 changes: 13 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,19 @@ exclude = [
"vulture_ignorelist.py",
]

# ty >= 0.0.86 rejects the `class Meta(<Base>.Meta)` idiom of Django models and
# DRF serializers as an invalid override, though the nested class subclasses the
# one it replaces (https://github.com/astral-sh/ty/issues/4711). Scoped to the
# models and serializers so the check still guards every other module; drop this
# once that issue is fixed, and then mark the two deliberate overrides it also
# hides, Volume.name and CustomCover.library, with
# `# ty: ignore[invalid-attribute-override]`.
[[tool.ty.overrides]]
include = ["codex/models/**", "codex/serializers/**"]

[tool.ty.overrides.rules]
invalid-attribute-override = "ignore"

[tool.vulture]
exclude = [
"*/.venv/*",
Expand Down