diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8a8f92b549..1f96a534bc 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -28,7 +28,7 @@ jobs:
- name: Build ext package
run: python -m build ext/
- name: Publish ext to PyPI
- uses: pypa/gh-action-pypi-publish@v1.12.3
+ uses: pypa/gh-action-pypi-publish@v1.12.4
with:
packages-dir: ext/dist/
@@ -53,4 +53,4 @@ jobs:
- name: Build package
run: python -m build
- name: Publish to PyPI
- uses: pypa/gh-action-pypi-publish@v1.12.3
+ uses: pypa/gh-action-pypi-publish@v1.12.4
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 4ee6ac4f37..2be5511701 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -17,13 +17,13 @@ repos:
args: [--fix=lf]
- id: check-case-conflict
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.9.1
+ rev: v0.9.4
hooks:
- id: ruff
args: ["--fix", "--exit-non-zero-on-fix"]
- id: ruff-format
- repo: https://github.com/codespell-project/codespell
- rev: v2.3.0
+ rev: v2.4.1
hooks:
- id: codespell
additional_dependencies:
diff --git a/README.md b/README.md
index b244592883..3fe024e4b7 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-[](https://github.com/typeddjango/django-stubs/actions?query=workflow%3Atest)
+[](https://github.com/typeddjango/django-stubs/actions/workflows/test.yml)
[](http://mypy-lang.org/)
[](https://gitter.im/mypy-django/Lobby)
[](https://stackoverflow.com/questions/tagged/django-stubs?tab=Active)
@@ -49,7 +49,8 @@ We rely on different `django` and `mypy` versions:
| django-stubs | Mypy version | Django version | Django partial support | Python version |
|----------------|--------------|----------------|------------------------|----------------|
-| 5.1.2 | 1.13+ | 5.1 | 4.2 | 3.8 - 3.13 |
+| 5.1.3 | 1.13+ | 5.1 | 4.2 | 3.9 - 3.13 |
+| 5.1.2 | 1.13+ | 5.1 | 4.2 | 3.9 - 3.13 |
| 5.1.1 | 1.13.x | 5.1 | 4.2 | 3.8 - 3.12 |
| 5.1.0 | 1.11.x | 5.1 | 4.2 | 3.8 - 3.12 |
| 5.0.4 | 1.11.x | 5.0 | 4.2 | 3.8 - 3.12 |
diff --git a/django-stubs/contrib/admin/options.pyi b/django-stubs/contrib/admin/options.pyi
index 02e172dc0c..1728034d0b 100644
--- a/django-stubs/contrib/admin/options.pyi
+++ b/django-stubs/contrib/admin/options.pyi
@@ -1,6 +1,6 @@
import enum
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
-from typing import Any, Generic, Literal, TypeVar, cast, type_check_only
+from typing import Any, Generic, Literal, TypeVar, cast, overload, type_check_only
from django import forms
from django.contrib.admin.filters import FieldListFilter, ListFilter
@@ -129,8 +129,11 @@ class BaseModelAdmin(Generic[_ModelT]):
def get_prepopulated_fields(self, request: HttpRequest, obj: _ModelT | None = ...) -> dict[str, Sequence[str]]: ...
def get_queryset(self, request: HttpRequest) -> QuerySet[_ModelT]: ...
def get_sortable_by(self, request: HttpRequest) -> _DisplayT[_ModelT]: ...
- @deprecated("The None value for the request parameter will be removed in Django 6.0.")
- def lookup_allowed(self, lookup: str, value: str, request: HttpRequest | None = ...) -> bool: ...
+ @overload
+ @deprecated("None value for the request parameter will be removed in Django 6.0.")
+ def lookup_allowed(self, lookup: str, value: str, request: None = None) -> bool: ...
+ @overload
+ def lookup_allowed(self, lookup: str, value: str, request: HttpRequest) -> bool: ...
def to_field_allowed(self, request: HttpRequest, to_field: str) -> bool: ...
def has_add_permission(self, request: HttpRequest) -> bool: ...
def has_change_permission(self, request: HttpRequest, obj: _ModelT | None = ...) -> bool: ...
diff --git a/django-stubs/contrib/contenttypes/models.pyi b/django-stubs/contrib/contenttypes/models.pyi
index e7206aed0a..1c15422e15 100644
--- a/django-stubs/contrib/contenttypes/models.pyi
+++ b/django-stubs/contrib/contenttypes/models.pyi
@@ -13,8 +13,8 @@ class ContentTypeManager(models.Manager[ContentType]):
class ContentType(models.Model):
id: int
- app_label: models.CharField
- model: models.CharField
+ app_label = models.CharField(max_length=100)
+ model = models.CharField(max_length=100)
objects: ClassVar[ContentTypeManager]
@property
def name(self) -> str: ...
diff --git a/django-stubs/contrib/gis/geoip2/base.pyi b/django-stubs/contrib/gis/geoip2/base.pyi
index 3f75eb18cd..30b83fa950 100644
--- a/django-stubs/contrib/gis/geoip2/base.pyi
+++ b/django-stubs/contrib/gis/geoip2/base.pyi
@@ -29,7 +29,7 @@ class GeoIP2:
def is_city(self) -> bool: ...
@cached_property
def is_country(self) -> bool: ...
- @deprecated("The coords() is deprecated and will be removed in Django 6.0. Use lon_lat() instead.")
+ @deprecated("coords() is deprecated and will be removed in Django 6.0. Use lon_lat() instead.")
def coords(self, query: str, ordering: Sequence[str] = ...) -> tuple[float, float] | tuple[None, None]: ...
def lon_lat(self, query: str) -> tuple[float, float] | tuple[None, None]: ...
def lat_lon(self, query: str) -> tuple[float, float] | tuple[None, None]: ...
@@ -37,5 +37,5 @@ class GeoIP2:
@property
def info(self) -> str: ...
@classmethod
- @deprecated("The open() is deprecated and will be removed in Django 6.0. Use GeoIP2() instead.")
+ @deprecated("open() is deprecated and will be removed in Django 6.0. Use GeoIP2() instead.")
def open(cls, full_path: Path | str | None, cache: int) -> Any: ...
diff --git a/django-stubs/core/serializers/xml_serializer.pyi b/django-stubs/core/serializers/xml_serializer.pyi
index c1b6e048fa..2762530fec 100644
--- a/django-stubs/core/serializers/xml_serializer.pyi
+++ b/django-stubs/core/serializers/xml_serializer.pyi
@@ -1,5 +1,5 @@
from typing import IO, Any
-from xml.sax.expatreader import ExpatParser as _ExpatParser # type: ignore[import-not-found]
+from xml.sax.expatreader import ExpatParser
from django.core.serializers import base
@@ -31,7 +31,7 @@ class Deserializer(base.Deserializer):
def getInnerText(node: Any) -> str: ...
-class DefusedExpatParser(_ExpatParser):
+class DefusedExpatParser(ExpatParser):
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
def start_doctype_decl(self, name: Any, sysid: Any, pubid: Any, has_internal_subset: Any) -> None: ...
def entity_decl(
diff --git a/django-stubs/core/validators.pyi b/django-stubs/core/validators.pyi
index cbe29a1b08..38933e038f 100644
--- a/django-stubs/core/validators.pyi
+++ b/django-stubs/core/validators.pyi
@@ -30,6 +30,21 @@ class RegexValidator(_Deconstructible):
) -> None: ...
def __call__(self, value: Any) -> None: ...
+class DomainNameValidator(RegexValidator):
+ ul: str
+ hostname_re: str
+ domain_re: str
+ tld_re: str
+ ascii_only_hostname_re: str
+ ascii_only_domain_re: str
+ ascii_only_tld_re: str
+ max_length: int
+
+ def __init__(self, *, accept_idna: bool = True, **kwargs: Any) -> None: ...
+ def __call__(self, value: Any) -> None: ...
+
+validate_domain_name: DomainNameValidator
+
class URLValidator(RegexValidator):
ul: str
ipv4_re: str
diff --git a/django-stubs/db/backends/base/operations.pyi b/django-stubs/db/backends/base/operations.pyi
index fc5eac0f87..f08480c926 100644
--- a/django-stubs/db/backends/base/operations.pyi
+++ b/django-stubs/db/backends/base/operations.pyi
@@ -45,7 +45,7 @@ class BaseDatabaseOperations:
def deferrable_sql(self) -> str: ...
def distinct_sql(self, fields: list[str], params: list[Any] | None) -> tuple[list[str], list[str]]: ...
def fetch_returned_insert_columns(self, cursor: Any, returning_params: Any) -> Any: ...
- @deprecated("The field_cast_sql() is deprecated and will be removed in Django 6.0. Use lookup_cast() instead.")
+ @deprecated("field_cast_sql() is deprecated and will be removed in Django 6.0. Use lookup_cast() instead.")
def field_cast_sql(self, db_type: str | None, internal_type: str) -> str: ...
def force_no_ordering(self) -> list[Any]: ...
def for_update_sql(
diff --git a/django-stubs/db/models/constraints.pyi b/django-stubs/db/models/constraints.pyi
index e3dd595859..beb69d87ff 100644
--- a/django-stubs/db/models/constraints.pyi
+++ b/django-stubs/db/models/constraints.pyi
@@ -47,7 +47,7 @@ class CheckConstraint(BaseConstraint):
condition: Q | BaseExpression
@overload
- @deprecated("The check keyword argument is deprecated in favor of condition and will be removed in Django 6.0")
+ @deprecated("check keyword argument is deprecated in favor of condition and will be removed in Django 6.0")
def __init__(
self,
*,
diff --git a/django-stubs/db/models/enums.pyi b/django-stubs/db/models/enums.pyi
index e511a4f01c..64328e0c09 100644
--- a/django-stubs/db/models/enums.pyi
+++ b/django-stubs/db/models/enums.pyi
@@ -4,7 +4,7 @@ from typing import Any, TypeVar, overload, type_check_only
from _typeshed import ConvertibleToInt
from django.utils.functional import _StrOrPromise
-from typing_extensions import TypeAlias, deprecated
+from typing_extensions import deprecated
_Self = TypeVar("_Self")
@@ -21,8 +21,7 @@ else:
class IntEnum(int, ReprEnum): ... # type: ignore[misc]
class StrEnum(str, ReprEnum): ... # type: ignore[misc]
-@deprecated("ChoicesMeta is deprecated in favor of ChoicesType and will be removed in Django 6.0.")
-class ChoicesMeta(EnumType):
+class ChoicesType(EnumType):
# There's a contradiction between mypy and PYI019 regarding metaclasses. Where mypy
# disallows 'typing_extensions.Self' on metaclasses, while PYI019 try to enforce
# 'typing_extensions.Self' for '__new__' methods.. We've chosen to ignore the
@@ -40,7 +39,8 @@ class ChoicesMeta(EnumType):
@property
def values(self) -> list[Any]: ...
-ChoicesType: TypeAlias = ChoicesMeta
+@deprecated("ChoicesMeta is deprecated in favor of ChoicesType and will be removed in Django 6.0.")
+class ChoicesMeta(ChoicesType): ...
class Choices(enum.Enum, metaclass=ChoicesType): # type: ignore[misc]
@property
diff --git a/django-stubs/db/models/fields/mixins.pyi b/django-stubs/db/models/fields/mixins.pyi
index fe6aaa2af4..297d3230d8 100644
--- a/django-stubs/db/models/fields/mixins.pyi
+++ b/django-stubs/db/models/fields/mixins.pyi
@@ -9,7 +9,7 @@ NOT_PROVIDED: Any
class FieldCacheMixin:
def get_cache_name(self) -> str: ...
@cached_property
- @deprecated("The cache_name() is deprecated and will be removed in Django 6.0. Use get_cache_name() instead.")
+ @deprecated("cache_name() is deprecated and will be removed in Django 6.0. Use get_cache_name() instead.")
def cache_name(self) -> str: ...
def get_cached_value(self, instance: Model, default: Any = ...) -> Model | None: ...
def is_cached(self, instance: Model) -> bool: ...
diff --git a/django-stubs/db/models/fields/reverse_related.pyi b/django-stubs/db/models/fields/reverse_related.pyi
index 2a7961de1c..30d23d33c0 100644
--- a/django-stubs/db/models/fields/reverse_related.pyi
+++ b/django-stubs/db/models/fields/reverse_related.pyi
@@ -79,7 +79,7 @@ class ForeignObjectRel(FieldCacheMixin):
) -> _ChoicesList: ...
def is_hidden(self) -> bool: ...
@deprecated(
- "The get_joining_columns() is deprecated and will be removed in Django 6.0. Use get_joining_fields() instead."
+ "get_joining_columns() is deprecated and will be removed in Django 6.0. Use get_joining_fields() instead."
)
def get_joining_columns(self) -> tuple: ...
def get_joining_fields(self) -> tuple[tuple[Field, Field], ...]: ...
diff --git a/django-stubs/db/models/query.pyi b/django-stubs/db/models/query.pyi
index d6d27f2134..3cc3fed92e 100644
--- a/django-stubs/db/models/query.pyi
+++ b/django-stubs/db/models/query.pyi
@@ -241,7 +241,7 @@ class Prefetch:
def get_current_prefetch_to(self, level: int) -> str: ...
def get_current_to_attr(self, level: int) -> tuple[str, str]: ...
@deprecated(
- "The get_current_queryset() is deprecated and will be removed in Django 6.0. Use get_current_querysets() instead."
+ "get_current_queryset() is deprecated and will be removed in Django 6.0. Use get_current_querysets() instead."
)
def get_current_queryset(self, level: int) -> QuerySet | None: ...
def get_current_querysets(self, level: int) -> list[QuerySet] | None: ...
diff --git a/django-stubs/forms/renderers.pyi b/django-stubs/forms/renderers.pyi
index a4ed0a296a..e88459c683 100644
--- a/django-stubs/forms/renderers.pyi
+++ b/django-stubs/forms/renderers.pyi
@@ -30,7 +30,7 @@ class Jinja2(EngineMixin, BaseRenderer):
def backend(self) -> type[Jinja2R]: ...
@deprecated(
- "The Jinja2DivFormRenderer transitional form renderer is deprecated and will be removed in Django 6.0. Use Jinja2 instead."
+ "Jinja2DivFormRenderer transitional form renderer is deprecated and will be removed in Django 6.0. Use Jinja2 instead."
)
class Jinja2DivFormRenderer(Jinja2):
form_template_name: str
diff --git a/django-stubs/test/testcases.pyi b/django-stubs/test/testcases.pyi
index 7b898c932b..464e01873b 100644
--- a/django-stubs/test/testcases.pyi
+++ b/django-stubs/test/testcases.pyi
@@ -152,13 +152,13 @@ class SimpleTestCase(unittest.TestCase):
def assertInHTML(self, needle: str, haystack: str, count: int | None = ..., msg_prefix: str = ...) -> None: ...
def assertJSONEqual(
self,
- raw: str,
+ raw: str | bytes | bytearray,
expected_data: dict[str, Any] | list[Any] | str | int | float | bool | None,
msg: str | None = ...,
) -> None: ...
def assertJSONNotEqual(
self,
- raw: str,
+ raw: str | bytes | bytearray,
expected_data: dict[str, Any] | list[Any] | str | int | float | bool | None,
msg: str | None = ...,
) -> None: ...
diff --git a/django-stubs/utils/html.pyi b/django-stubs/utils/html.pyi
index 38e059cffc..87ddb2df36 100644
--- a/django-stubs/utils/html.pyi
+++ b/django-stubs/utils/html.pyi
@@ -3,7 +3,7 @@ from functools import cached_property
from html.parser import HTMLParser
from json import JSONEncoder
from re import Pattern
-from typing import Any
+from typing import Any, overload
from _typeshed import Incomplete
from django.utils.functional import SimpleLazyObject, _StrOrPromise
@@ -20,7 +20,10 @@ def json_script(value: Any, element_id: str | None = None, encoder: type[JSONEnc
# conditional_escape could use a protocol to be more precise, see https://github.com/typeddjango/django-stubs/issues/1474
def conditional_escape(text: _StrOrPromise | SafeData) -> SafeString: ...
+@overload
@deprecated("Calling format_html() without passing args or kwargs is deprecated.")
+def format_html(format_string: str) -> SafeString: ...
+@overload
def format_html(format_string: str, *args: Any, **kwargs: Any) -> SafeString: ...
def format_html_join(sep: str, format_string: str, args_generator: Iterable[Iterable[Any]]) -> SafeString: ...
def linebreaks(value: Any, autoescape: bool = False) -> str: ...
diff --git a/django-stubs/utils/ipv6.pyi b/django-stubs/utils/ipv6.pyi
index 8ee5ecbb46..e88dcf2778 100644
--- a/django-stubs/utils/ipv6.pyi
+++ b/django-stubs/utils/ipv6.pyi
@@ -1,4 +1,9 @@
+from ipaddress import IPv6Address
from typing import Any
-def clean_ipv6_address(ip_str: Any, unpack_ipv4: bool = False, error_message: str = ...) -> str: ...
-def is_valid_ipv6_address(ip_str: str) -> bool: ...
+MAX_IPV6_ADDRESS_LENGTH: int
+
+def clean_ipv6_address(
+ ip_str: Any, unpack_ipv4: bool = False, error_message: str = ..., max_length: int = 39
+) -> str: ...
+def is_valid_ipv6_address(ip_str: str | IPv6Address) -> bool: ...
diff --git a/django-stubs/utils/itercompat.pyi b/django-stubs/utils/itercompat.pyi
index 379e860371..8023c9e403 100644
--- a/django-stubs/utils/itercompat.pyi
+++ b/django-stubs/utils/itercompat.pyi
@@ -3,6 +3,6 @@ from typing import Any
from typing_extensions import deprecated
@deprecated(
- "The django.utils.itercompat.is_iterable() is deprecated and will be removed in Django 6.0. Use isinstance(..., collections.abc.Iterable) instead."
+ "django.utils.itercompat.is_iterable() is deprecated and will be removed in Django 6.0. Use isinstance(..., collections.abc.Iterable) instead."
)
def is_iterable(x: Any) -> bool: ...
diff --git a/ext/setup.py b/ext/setup.py
index 4137ad3e51..8732472dfb 100755
--- a/ext/setup.py
+++ b/ext/setup.py
@@ -13,7 +13,7 @@
# It's fine to skip django-stubs-ext releases, but when doing a release, update this to newest django-stubs version.
setup(
name="django-stubs-ext",
- version="5.1.2",
+ version="5.1.3",
description="Monkey-patching and extensions for django-stubs",
long_description=readme,
long_description_content_type="text/markdown",
diff --git a/mypy.ini b/mypy.ini
index 428791b818..774e0fc2a6 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -18,6 +18,8 @@ disable_error_code = empty-body
force_uppercase_builtins = true
force_union_syntax = true
+enable_error_code = deprecated
+
plugins =
mypy_django_plugin.main,
diff --git a/mypy_django_plugin/transformers/fields.py b/mypy_django_plugin/transformers/fields.py
index f2bd21009b..0a1f1c435d 100644
--- a/mypy_django_plugin/transformers/fields.py
+++ b/mypy_django_plugin/transformers/fields.py
@@ -166,11 +166,11 @@ def set_descriptor_types_for_field(
if not (isinstance(mapped_set_type, UninhabitedType) or isinstance(mapped_get_type, UninhabitedType)):
# always replace set_type and get_type with (non-Any) mapped types
set_type = helpers.convert_any_to_type(mapped_set_type, set_type)
- get_type = helpers.convert_any_to_type(mapped_get_type, get_type)
+ get_type = get_proper_type(helpers.convert_any_to_type(mapped_get_type, get_type))
# the get_type must be optional if the field is nullable
if (is_get_nullable or is_nullable) and not (
- isinstance(get_proper_type(get_type), NoneType) or helpers.is_optional(get_type)
+ isinstance(get_type, NoneType) or helpers.is_optional(get_type) or isinstance(get_type, AnyType)
):
ctx.api.fail(
f"{default_return_type.type.name} is nullable but its generic get type parameter is not optional",
diff --git a/requirements.txt b/requirements.txt
index 58745b1a60..e066fe559c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
# Dev tools:
-pre-commit==4.0.1
+pre-commit==4.1.0
pytest==8.3.4
pytest-mypy-plugins==3.2.0
pytest-shard==0.1.2
@@ -7,10 +7,10 @@ pytest-shard==0.1.2
# Django deps:
psycopg2-binary
Django==4.2.16; python_version < '3.10'
-Django==5.1.4; python_version >= '3.10'
+Django==5.1.5; python_version >= '3.10'
-e ./ext
-e .[redis,compatible-mypy,oracle]
# Overrides:
-mypy==1.14.1
-pyright==1.1.391
+mypy==1.15.0
+pyright==1.1.393
diff --git a/scripts/stubtest/allowlist_todo_django51.txt b/scripts/stubtest/allowlist_todo_django51.txt
index 595cac4ab3..0245d995eb 100644
--- a/scripts/stubtest/allowlist_todo_django51.txt
+++ b/scripts/stubtest/allowlist_todo_django51.txt
@@ -100,8 +100,6 @@ django.core.files.storage.filesystem.FileSystemStorage.__init__
django.core.files.storage.filesystem.FileSystemStorage.is_name_available
django.core.files.storage.get_storage_class
django.core.signing.Signer.__init__
-django.core.validators.DomainNameValidator
-django.core.validators.validate_domain_name
django.db.backends.base.features.BaseDatabaseFeatures.supports_frame_exclusion
django.db.backends.base.operations.BaseDatabaseOperations.bulk_insert_sql
django.db.backends.base.operations.BaseDatabaseOperations.force_group_by
diff --git a/setup.py b/setup.py
index c375e5cb33..4eeb55dc01 100644
--- a/setup.py
+++ b/setup.py
@@ -22,7 +22,7 @@ def find_stub_files(name: str) -> list[str]:
dependencies = [
"django",
"asgiref",
- "django-stubs-ext>=5.1.2",
+ "django-stubs-ext>=5.1.3",
"tomli; python_version < '3.11'",
# Types:
"typing-extensions>=4.11.0",
@@ -31,14 +31,14 @@ def find_stub_files(name: str) -> list[str]:
# Keep compatible-mypy major.minor version pinned to what we use in CI (requirements.txt)
extras_require = {
- "compatible-mypy": ["mypy>=1.12,<1.15"],
+ "compatible-mypy": ["mypy>=1.12,<1.16"],
"redis": ["redis"],
"oracle": ["oracledb"],
}
setup(
name="django-stubs",
- version="5.1.2",
+ version="5.1.3",
description="Mypy stubs for Django",
long_description=readme,
long_description_content_type="text/markdown",
diff --git a/tests/typecheck/contrib/contenttypes/test_models.yml b/tests/typecheck/contrib/contenttypes/test_models.yml
new file mode 100644
index 0000000000..284de2b1a8
--- /dev/null
+++ b/tests/typecheck/contrib/contenttypes/test_models.yml
@@ -0,0 +1,13 @@
+- case: test_contenttypes_models
+ main: |
+ from django.contrib.contenttypes.models import ContentType
+
+ c = ContentType.objects.create(app_label='abc', model='abc')
+ reveal_type(c.id) # N: Revealed type is "builtins.int"
+ reveal_type(c.app_label) # N: Revealed type is "builtins.str"
+ reveal_type(c.model) # N: Revealed type is "builtins.str"
+
+ ContentType.objects.create(app_label=[]) # E: Incompatible type for "app_label" of "ContentType" (got "List[Any]", expected "Union[str, int, Combinable]") [misc]
+
+ custom_settings: |
+ INSTALLED_APPS = ("django.contrib.contenttypes",)
diff --git a/tests/typecheck/fields/test_custom_fields.yml b/tests/typecheck/fields/test_custom_fields.yml
index 14d175348a..335de9a551 100644
--- a/tests/typecheck/fields/test_custom_fields.yml
+++ b/tests/typecheck/fields/test_custom_fields.yml
@@ -18,13 +18,12 @@
reveal_type(user.my_custom_field13) # N: Revealed type is "Union[myapp.models.CustomFieldValue, None]"
reveal_type(user.my_custom_field14) # N: Revealed type is "Union[builtins.bool, None]"
reveal_type(user.my_custom_field15) # N: Revealed type is "None"
+
+ reveal_type(user.my_custom_field_any1) # N: Revealed type is "Any"
+ reveal_type(user.my_custom_field_any2) # N: Revealed type is "Any"
+ reveal_type(user.my_custom_field_any3) # N: Revealed type is "Any"
+ reveal_type(user.my_custom_field_any4) # N: Revealed type is "Any"
monkeypatch: true
- out: |
- myapp/models:31: error: GenericField is nullable but its generic get type parameter is not optional [misc]
- myapp/models:32: error: CustomValueField is nullable but its generic get type parameter is not optional [misc]
- myapp/models:33: error: SingleTypeField is nullable but its generic get type parameter is not optional [misc]
- myapp/models:34: error: AdditionalTypeVarField is nullable but its generic get type parameter is not optional [misc]
- myapp/models:35: error: Field is nullable but its generic get type parameter is not optional [misc]
installed_apps:
- myapp
files:
@@ -53,19 +52,24 @@
class CustomSmallIntegerField(fields.SmallIntegerField[_ST, _GT]): ...
+ class FieldImplicitAny(fields.Field): ...
+ class FieldExplicitAny(fields.Field[Any, Any]): ...
+
class User(models.Model):
id = models.AutoField(primary_key=True)
my_custom_field1 = GenericField[Union[CustomFieldValue, int], CustomFieldValue]()
my_custom_field2 = CustomValueField()
my_custom_field3 = SingleTypeField[bool]()
my_custom_field4 = AdditionalTypeVarField[Union[CustomFieldValue, int], CustomFieldValue, bool]()
+ my_custom_field_any1 = FieldImplicitAny()
+ my_custom_field_any2 = FieldExplicitAny()
# test null=True on fields with non-optional generic types throw error
- my_custom_field5 = GenericField[Union[CustomFieldValue, int], CustomFieldValue](null=True)
- my_custom_field6 = CustomValueField(null=True)
- my_custom_field7 = SingleTypeField[bool](null=True)
- my_custom_field8 = AdditionalTypeVarField[Union[CustomFieldValue, int], CustomFieldValue, bool](null=True)
- my_custom_field9 = fields.Field[Union[CustomFieldValue, int], CustomFieldValue](null=True)
+ my_custom_field5 = GenericField[Union[CustomFieldValue, int], CustomFieldValue](null=True) # E: GenericField is nullable but its generic get type parameter is not optional [misc]
+ my_custom_field6 = CustomValueField(null=True) # E: CustomValueField is nullable but its generic get type parameter is not optional [misc]
+ my_custom_field7 = SingleTypeField[bool](null=True) # E: SingleTypeField is nullable but its generic get type parameter is not optional [misc]
+ my_custom_field8 = AdditionalTypeVarField[Union[CustomFieldValue, int], CustomFieldValue, bool](null=True) # E: AdditionalTypeVarField is nullable but its generic get type parameter is not optional [misc]
+ my_custom_field9 = fields.Field[Union[CustomFieldValue, int], CustomFieldValue](null=True) # E: Field is nullable but its generic get type parameter is not optional [misc]
# test overriding fields that set _pyi_private_set_type or _pyi_private_get_type
my_custom_field10 = fields.SmallIntegerField[bool, bool]()
@@ -76,3 +80,7 @@
my_custom_field13 = GenericField[Union[CustomFieldValue, int], Union[CustomFieldValue, None]](null=True)
my_custom_field14 = SingleTypeField[Union[bool, None]](null=True)
my_custom_field15 = fields.Field[None, None](null=True)
+
+ # test null=True on Any does not raise
+ my_custom_field_any3 = FieldImplicitAny(null=True)
+ my_custom_field_any4 = FieldExplicitAny(null=True)
diff --git a/tests/typecheck/managers/querysets/test_from_queryset.yml b/tests/typecheck/managers/querysets/test_from_queryset.yml
index 60f6c765f0..5747cac029 100644
--- a/tests/typecheck/managers/querysets/test_from_queryset.yml
+++ b/tests/typecheck/managers/querysets/test_from_queryset.yml
@@ -948,7 +948,7 @@
main:12: note: Revealed type is "Type[django.db.models.manager.Manager[django.db.models.base.Model]]"
main:12: error: Argument 1 to "from_queryset" of "BaseManager" has incompatible type ""; expected "Type[QuerySet[Model, Model]]" [arg-type]
main:17: note: Revealed type is "Type[django.db.models.manager.Manager[django.db.models.base.Model]]"
- main:17: error: Argument 1 to "from_queryset" of "BaseManager" has incompatible type "Type[NonQSGeneric[Any]]"; expected "Type[QuerySet[Model, Model]]" [arg-type]
+ main:17: error: Argument 1 to "from_queryset" of "BaseManager" has incompatible type "Type[NonQSGeneric[int]]"; expected "Type[QuerySet[Model, Model]]" [arg-type]
- case: test_reverse_manager_with_foreign_key
main: |