Skip to content

Commit

Permalink
Simulate @deconstructible as a mixin class (#1818)
Browse files Browse the repository at this point in the history
`@desconstructible` inserts its own `__new__` method and adds a
`deconstruct` method to decorated classes. We can simulate that
behaviour with a class definition, which decorated classes inherits.
  • Loading branch information
flaeppe authored Nov 5, 2023
1 parent 7e0b7e8 commit d0e5df2
Show file tree
Hide file tree
Showing 9 changed files with 32 additions and 103 deletions.
10 changes: 2 additions & 8 deletions django-stubs/contrib/auth/validators.pyi
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
from collections.abc import Sequence
from typing import Any

from django.core.validators import RegexValidator

class ASCIIUsernameValidator(RegexValidator):
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

class UnicodeUsernameValidator(RegexValidator):
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...
class ASCIIUsernameValidator(RegexValidator): ...
class UnicodeUsernameValidator(RegexValidator): ...
5 changes: 2 additions & 3 deletions django-stubs/contrib/gis/geos/geometry.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from collections.abc import Sequence
from typing import Any

from django.contrib.gis.gdal import CoordTransform, SpatialReference
Expand All @@ -8,6 +7,7 @@ from django.contrib.gis.geos.coordseq import GEOSCoordSeq
from django.contrib.gis.geos.mutable_list import ListMixin
from django.contrib.gis.geos.point import Point
from django.contrib.gis.geos.prepared import PreparedGeometry
from django.utils.deconstruct import _Deconstructible
from typing_extensions import Self

class GEOSGeometryBase(GEOSBase):
Expand Down Expand Up @@ -142,6 +142,5 @@ class LinearGeometryMixin:
@property
def closed(self) -> bool: ...

class GEOSGeometry(GEOSGeometryBase, ListMixin):
class GEOSGeometry(_Deconstructible, GEOSGeometryBase, ListMixin):
def __init__(self, geo_input: Any, srid: int | None = ...) -> None: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...
6 changes: 3 additions & 3 deletions django-stubs/contrib/postgres/validators.pyi
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
from collections.abc import Iterable, Mapping, Sequence
from collections.abc import Iterable, Mapping
from typing import Any

from django.core.validators import MaxLengthValidator, MaxValueValidator, MinLengthValidator, MinValueValidator
from django.utils.deconstruct import _Deconstructible

class ArrayMaxLengthValidator(MaxLengthValidator): ...
class ArrayMinLengthValidator(MinLengthValidator): ...

class KeysValidator:
class KeysValidator(_Deconstructible):
messages: dict[str, str]
strict: bool
def __init__(self, keys: Iterable[str], strict: bool = ..., messages: Mapping[str, str] | None = ...) -> None: ...
def __call__(self, value: Any) -> None: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ... # fake

class RangeMaxValueValidator(MaxValueValidator): ...
class RangeMinValueValidator(MinValueValidator): ...
7 changes: 2 additions & 5 deletions django-stubs/core/files/storage/filesystem.pyi
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
from collections.abc import Sequence
from typing import Any

from django.utils._os import _PathCompatible
from django.utils.deconstruct import _Deconstructible
from django.utils.functional import cached_property

from .base import Storage
from .mixins import StorageSettingsMixin

class FileSystemStorage(Storage, StorageSettingsMixin):
class FileSystemStorage(_Deconstructible, Storage, StorageSettingsMixin):
OS_OPEN_FLAGS: int

def __init__(
Expand All @@ -27,4 +25,3 @@ class FileSystemStorage(Storage, StorageSettingsMixin):
def file_permissions_mode(self) -> int | None: ...
@cached_property
def directory_permissions_mode(self) -> int | None: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ... # fake
7 changes: 2 additions & 5 deletions django-stubs/core/files/storage/memory.pyi
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
from collections.abc import Sequence
from typing import Any

from django.utils._os import _PathCompatible
from django.utils.deconstruct import _Deconstructible
from django.utils.functional import cached_property

from .base import Storage
from .mixins import StorageSettingsMixin

class InMemoryStorage(Storage, StorageSettingsMixin):
class InMemoryStorage(_Deconstructible, Storage, StorageSettingsMixin):
def __init__(
self,
location: _PathCompatible | None = ...,
Expand All @@ -25,4 +23,3 @@ class InMemoryStorage(Storage, StorageSettingsMixin):
def file_permissions_mode(self) -> int | None: ...
@cached_property
def directory_permissions_mode(self) -> int | None: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ... # fake
33 changes: 10 additions & 23 deletions django-stubs/core/validators.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ from re import Pattern, RegexFlag
from typing import Any

from django.core.files.base import File
from django.utils.deconstruct import _Deconstructible
from django.utils.functional import _StrOrPromise
from typing_extensions import TypeAlias

Expand All @@ -13,7 +14,7 @@ _Regex: TypeAlias = str | Pattern[str]

_ValidatorCallable: TypeAlias = Callable[[Any], None] # noqa: PYI047

class RegexValidator:
class RegexValidator(_Deconstructible):
regex: _Regex # Pattern[str] on instance, but may be str on class definition
message: _StrOrPromise
code: str
Expand All @@ -28,7 +29,6 @@ class RegexValidator:
flags: RegexFlag | None = ...,
) -> None: ...
def __call__(self, value: Any) -> None: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

class URLValidator(RegexValidator):
ul: str
Expand All @@ -43,13 +43,12 @@ class URLValidator(RegexValidator):
max_length: int
def __init__(self, schemes: Sequence[str] | None = ..., **kwargs: Any) -> None: ...
def __call__(self, value: str) -> None: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

integer_validator: RegexValidator

def validate_integer(value: float | str | None) -> None: ...

class EmailValidator:
class EmailValidator(_Deconstructible):
message: _StrOrPromise
code: str
user_regex: Pattern[str]
Expand All @@ -65,7 +64,6 @@ class EmailValidator:
def __call__(self, value: str | None) -> None: ...
def validate_domain_part(self, domain_part: str) -> bool: ...
def __eq__(self, other: object) -> bool: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

validate_email: EmailValidator
slug_re: Pattern[str]
Expand All @@ -87,7 +85,7 @@ def int_list_validator(

validate_comma_separated_integer_list: RegexValidator

class BaseValidator:
class BaseValidator(_Deconstructible):
message: _StrOrPromise
code: str
limit_value: Any
Expand All @@ -96,35 +94,26 @@ class BaseValidator:
def compare(self, a: Any, b: Any) -> bool: ...
def clean(self, x: Any) -> Any: ...
def __eq__(self, other: object) -> bool: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

class MaxValueValidator(BaseValidator):
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

class MinValueValidator(BaseValidator):
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

class StepValueValidator(BaseValidator):
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...
class MaxValueValidator(BaseValidator): ...
class MinValueValidator(BaseValidator): ...
class StepValueValidator(BaseValidator): ...

class MinLengthValidator(BaseValidator):
def clean(self, x: Sized) -> int: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

class MaxLengthValidator(BaseValidator):
def clean(self, x: Sized) -> int: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

class DecimalValidator:
class DecimalValidator(_Deconstructible):
messages: dict[str, str]
max_digits: int | None
decimal_places: int | None
def __init__(self, max_digits: int | None, decimal_places: int | None) -> None: ...
def __call__(self, value: Decimal) -> None: ...
def __eq__(self, other: object) -> bool: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

class FileExtensionValidator:
class FileExtensionValidator(_Deconstructible):
message: _StrOrPromise
code: str
allowed_extensions: Collection[str] | None
Expand All @@ -135,14 +124,12 @@ class FileExtensionValidator:
code: str | None = ...,
) -> None: ...
def __call__(self, value: File) -> None: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

def get_available_image_extensions() -> Sequence[str]: ...
def validate_image_file_extension(value: File) -> None: ...

class ProhibitNullCharactersValidator:
class ProhibitNullCharactersValidator(_Deconstructible):
message: _StrOrPromise
code: str
def __init__(self, message: _StrOrPromise | None = ..., code: str | None = ...) -> None: ...
def __call__(self, value: Any) -> None: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...
13 changes: 3 additions & 10 deletions django-stubs/db/models/expressions.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ from django.db.models.lookups import Lookup, Transform
from django.db.models.query import QuerySet
from django.db.models.sql.compiler import SQLCompiler, _AsSqlType
from django.db.models.sql.query import Query
from django.utils.deconstruct import _Deconstructible
from typing_extensions import Self, TypeAlias

class SQLiteNumericMixin:
Expand Down Expand Up @@ -108,8 +109,7 @@ class BaseExpression:
def flatten(self) -> Iterator[BaseExpression]: ...
def as_sql(self, compiler: SQLCompiler, connection: BaseDatabaseWrapper) -> _AsSqlType: ...

class Expression(BaseExpression, Combinable):
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...
class Expression(_Deconstructible, BaseExpression, Combinable): ...

class CombinedExpression(SQLiteNumericMixin, Expression):
connector: str
Expand All @@ -123,7 +123,7 @@ class DurationExpression(CombinedExpression):
class TemporalSubtraction(CombinedExpression):
def __init__(self, lhs: Combinable, rhs: Combinable) -> None: ...

class F(Combinable):
class F(_Deconstructible, Combinable):
name: str
def __init__(self, name: str) -> None: ...
def resolve_expression(
Expand All @@ -149,7 +149,6 @@ class F(Combinable):
nulls_last: bool | None = ...,
) -> OrderBy: ...
def copy(self) -> F: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

class ResolvedOuterRef(F):
contains_aggregate: ClassVar[bool]
Expand Down Expand Up @@ -179,12 +178,10 @@ class Func(SQLiteNumericMixin, Expression):
arg_joiner: str | None = ...,
**extra_context: Any,
) -> _AsSqlType: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

class Value(Expression):
value: Any
def __init__(self, value: Any, output_field: Field | None = ...) -> None: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

class RawSQL(Expression):
params: list[Any]
Expand Down Expand Up @@ -213,7 +210,6 @@ _E = TypeVar("_E", bound=Q | Combinable)
class ExpressionWrapper(Expression, Generic[_E]):
def __init__(self, expression: _E, output_field: Field) -> None: ...
expression: _E
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

class NegatedExpression(ExpressionWrapper[_E]):
def __init__(self, expression: _E) -> None: ...
Expand All @@ -224,7 +220,6 @@ class When(Expression):
condition: Any
result: Any
def __init__(self, condition: Any = ..., then: Any = ..., **lookups: Any) -> None: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

class Case(Expression):
template: str
Expand All @@ -235,7 +230,6 @@ class Case(Expression):
def __init__(
self, *cases: Any, default: Any | None = ..., output_field: Field | None = ..., **extra: Any
) -> None: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

class Subquery(BaseExpression, Combinable):
template: str
Expand All @@ -261,7 +255,6 @@ class OrderBy(Expression):
) -> None: ...
def asc(self) -> None: ... # type: ignore[override]
def desc(self) -> None: ... # type: ignore[override]
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

class Window(SQLiteNumericMixin, Expression):
template: str
Expand Down
9 changes: 8 additions & 1 deletion django-stubs/utils/deconstruct.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from collections.abc import Callable
from collections.abc import Callable, Sequence
from typing import Any, TypeVar, overload

from typing_extensions import Self

# Contains additions from a class being decorated with '@deconstructible'
class _Deconstructible:
def __new__(cls, *args: Any, **kwargs: Any) -> Self: ...
def deconstruct(obj) -> tuple[str, Sequence[Any], dict[str, Any]]: ...

_T = TypeVar("_T")
_TCallable = TypeVar("_TCallable", bound=Callable[..., Any])

Expand Down
Loading

0 comments on commit d0e5df2

Please sign in to comment.