diff --git a/django-stubs/utils/functional.pyi b/django-stubs/utils/functional.pyi index 20d396059..ff667254e 100644 --- a/django-stubs/utils/functional.pyi +++ b/django-stubs/utils/functional.pyi @@ -1,4 +1,7 @@ from collections.abc import Callable, Sequence + +# Mypy has special handling for functools.cached_property, reuse typeshed's definition instead of defining our own +from functools import cached_property as cached_property from typing import Any, Generic, Protocol, SupportsIndex, TypeVar, overload from django.db.models.base import Model @@ -6,16 +9,6 @@ from typing_extensions import Self, TypeAlias _T = TypeVar("_T") -class cached_property(Generic[_T]): - func: Callable[[Any], _T] - name: str | None - def __init__(self, func: Callable[[Any], _T], name: str | None = ...) -> None: ... - @overload - def __get__(self, instance: None, cls: type[Any] | None = ...) -> Self: ... - @overload - def __get__(self, instance: object, cls: type[Any] | None = ...) -> _T: ... - def __set_name__(self, owner: type[Any], name: str) -> None: ... - # Promise is only subclassed by a proxy class defined in the lazy function # so it makes sense for it to have all the methods available in that proxy class class Promise: diff --git a/scripts/stubtest/allowlist.txt b/scripts/stubtest/allowlist.txt index 57feee188..c43230069 100644 --- a/scripts/stubtest/allowlist.txt +++ b/scripts/stubtest/allowlist.txt @@ -113,3 +113,46 @@ django.middleware.csrf.REASON_BAD_TOKEN # RemovedInDjango41 django.core.cache.backends.memcached.MemcachedCache + +# We re-export `functools.cached_property` which has different semantics +django.utils.functional.cached_property.__class_getitem__ +django.utils.functional.cached_property.__init__ +django.utils.functional.cached_property.__set__ +django.utils.functional.cached_property.name + +# Ignore @cached_property error "cannot reconcile @property on stub with runtime object" +django.db.migrations.RenameField.new_name_lower +django.db.migrations.RenameField.old_name_lower +django.db.migrations.RenameIndex.new_name_lower +django.db.migrations.RenameIndex.old_name_lower +django.db.migrations.RenameModel.new_name_lower +django.db.migrations.RenameModel.old_name_lower +django.db.migrations.operations.RenameField.new_name_lower +django.db.migrations.operations.RenameField.old_name_lower +django.db.migrations.operations.RenameIndex.new_name_lower +django.db.migrations.operations.RenameIndex.old_name_lower +django.db.migrations.operations.RenameModel.new_name_lower +django.db.migrations.operations.RenameModel.old_name_lower +django.db.migrations.operations.fields.FieldOperation.model_name_lower +django.db.migrations.operations.fields.FieldOperation.name_lower +django.db.migrations.operations.fields.RenameField.new_name_lower +django.db.migrations.operations.fields.RenameField.old_name_lower +django.db.migrations.operations.models.AlterTogetherOptionOperation.option_value +django.db.migrations.operations.models.IndexOperation.model_name_lower +django.db.migrations.operations.models.ModelOperation.name_lower +django.db.migrations.operations.models.RenameIndex.new_name_lower +django.db.migrations.operations.models.RenameIndex.old_name_lower +django.db.migrations.operations.models.RenameModel.new_name_lower +django.db.migrations.operations.models.RenameModel.old_name_lower +django.db.migrations.state.ModelState.name_lower +django.db.migrations.state.ProjectState.apps +django.middleware.csrf.CsrfViewMiddleware.allowed_origin_subdomains +django.middleware.csrf.CsrfViewMiddleware.allowed_origins_exact +django.middleware.csrf.CsrfViewMiddleware.csrf_trusted_origins_hosts +django.urls.URLPattern.lookup_str +django.urls.URLResolver.url_patterns +django.urls.URLResolver.urlconf_module +django.urls.resolvers.URLPattern.lookup_str +django.urls.resolvers.URLResolver.url_patterns +django.urls.resolvers.URLResolver.urlconf_module +django.utils.connection.BaseConnectionHandler.settings diff --git a/tests/typecheck/utils/test_functional.yml b/tests/typecheck/utils/test_functional.yml index 7ab5a9c45..65e0b50d9 100644 --- a/tests/typecheck/utils/test_functional.yml +++ b/tests/typecheck/utils/test_functional.yml @@ -1,23 +1,36 @@ - case: cached_property_class_vs_instance_attributes main: | from django.utils.functional import cached_property - from typing import List + from typing import List, ClassVar class Foo: @cached_property def attr(self) -> List[str]: ... - @cached_property # E: Argument 1 to "cached_property" has incompatible type "Callable[[Foo, str], List[str]]"; expected "Callable[[Any], List[str]]" [arg-type] + + @cached_property # E: Too many arguments for property [misc] def attr2(self, arg2: str) -> List[str]: ... - reveal_type(attr) # N: Revealed type is "django.utils.functional.cached_property[builtins.list[builtins.str]]" - reveal_type(attr.name) # N: Revealed type is "Union[builtins.str, None]" + reveal_type(attr) # N: Revealed type is "def (self: main.Foo) -> builtins.list[builtins.str]" - reveal_type(Foo.attr) # N: Revealed type is "django.utils.functional.cached_property[builtins.list[builtins.str]]" - reveal_type(Foo.attr.func) # N: Revealed type is "def (Any) -> builtins.list[builtins.str]" + reveal_type(Foo.attr) # N: Revealed type is "def (self: main.Foo) -> builtins.list[builtins.str]" f = Foo() - reveal_type(f.attr) # N: Revealed type is "builtins.list[builtins.str]" - f.attr.name # E: "List[str]" has no attribute "name" [attr-defined] + reveal_type(f.attr) # N: Revealed type is "builtins.list[builtins.str]" + f.attr.func # E: "List[str]" has no attribute "func" [attr-defined] + + # May be overridden by @property + class Bar(Foo): + @property + def attr(self) -> List[str]: ... + + # May be overridden by ClassVar + class Quux(Foo): + attr: ClassVar[List[str]] = [] + + # ClassVar may not be overridden by cached_property + class Baz(Quux): + @cached_property + def attr(self) -> List[str]: ... # E: Cannot override writeable attribute with read-only property [override] - case: str_promise_proxy main: |