diff --git a/django-stubs/conf/locale/__init__.pyi b/django-stubs/conf/locale/__init__.pyi new file mode 100644 index 0000000..4bd5cdf --- /dev/null +++ b/django-stubs/conf/locale/__init__.pyi @@ -0,0 +1,3 @@ +from typing import Dict, Any + +LANG_INFO: Dict[str, Any] = ... diff --git a/django-stubs/contrib/auth/base_user.pyi b/django-stubs/contrib/auth/base_user.pyi index f64b2fe..f9179d2 100644 --- a/django-stubs/contrib/auth/base_user.pyi +++ b/django-stubs/contrib/auth/base_user.pyi @@ -10,7 +10,7 @@ class BaseUserManager(models.Manager): class AbstractBaseUser(models.Model): password: models.CharField = ... - last_login: Optional[models.DateTimeField] = ... + last_login: models.DateTimeField = ... is_active: models.BooleanField = ... REQUIRED_FIELDS: List[str] = ... class Meta: ... diff --git a/django-stubs/contrib/auth/hashers.pyi b/django-stubs/contrib/auth/hashers.pyi index d5bd9ad..4998dfe 100644 --- a/django-stubs/contrib/auth/hashers.pyi +++ b/django-stubs/contrib/auth/hashers.pyi @@ -23,7 +23,7 @@ class BasePasswordHasher: memory_cost: int = ... parallelism: int = ... digest: Any = ... - iterations: Optional[int] = ... + iterations: int = ... def salt(self) -> str: ... def verify(self, password: str, encoded: str) -> bool: ... def encode(self, password: str, salt: str) -> Any: ... diff --git a/django-stubs/contrib/auth/management/commands/createsuperuser.pyi b/django-stubs/contrib/auth/management/commands/createsuperuser.pyi index 3490982..57d205d 100644 --- a/django-stubs/contrib/auth/management/commands/createsuperuser.pyi +++ b/django-stubs/contrib/auth/management/commands/createsuperuser.pyi @@ -1,4 +1,9 @@ +import getpass as getpass +from typing import Any + from django.core.management.base import BaseCommand class NotRunningInTTYException(Exception): ... -class Command(BaseCommand): ... + +class Command(BaseCommand): + stdin: Any diff --git a/django-stubs/contrib/auth/password_validation.pyi b/django-stubs/contrib/auth/password_validation.pyi index c37ae11..7eb1079 100644 --- a/django-stubs/contrib/auth/password_validation.pyi +++ b/django-stubs/contrib/auth/password_validation.pyi @@ -2,21 +2,20 @@ from pathlib import Path, PosixPath from typing import Any, List, Mapping, Optional, Protocol, Sequence, Set, Union from django.contrib.auth.base_user import AbstractBaseUser +from django.db.models.base import Model + +_UserModel = Model class PasswordValidator(Protocol): - def validate(self, password: str, user: Optional[AbstractBaseUser] = ...): ... + def password_changed(self, password: str, user: Optional[_UserModel] = ...): ... def get_default_password_validators() -> List[PasswordValidator]: ... def get_password_validators(validator_config: Sequence[Mapping[str, Any]]) -> List[PasswordValidator]: ... def validate_password( - password: str, - user: Optional[AbstractBaseUser] = ..., - password_validators: Optional[Sequence[PasswordValidator]] = ..., + password: str, user: Optional[_UserModel] = ..., password_validators: Optional[Sequence[PasswordValidator]] = ... ) -> None: ... def password_changed( - password: str, - user: Optional[AbstractBaseUser] = ..., - password_validators: Optional[Sequence[PasswordValidator]] = ..., + password: str, user: Optional[_UserModel] = ..., password_validators: Optional[Sequence[PasswordValidator]] = ... ) -> None: ... def password_validators_help_texts(password_validators: Optional[Sequence[PasswordValidator]] = ...) -> List[str]: ... @@ -25,7 +24,7 @@ password_validators_help_text_html: Any class MinimumLengthValidator: min_length: int = ... def __init__(self, min_length: int = ...) -> None: ... - def validate(self, password: str, user: Optional[AbstractBaseUser] = ...) -> None: ... + def validate(self, password: str, user: Optional[_UserModel] = ...) -> None: ... def get_help_text(self) -> str: ... class UserAttributeSimilarityValidator: @@ -33,16 +32,16 @@ class UserAttributeSimilarityValidator: user_attributes: Sequence[str] = ... max_similarity: float = ... def __init__(self, user_attributes: Sequence[str] = ..., max_similarity: float = ...) -> None: ... - def validate(self, password: str, user: Optional[AbstractBaseUser] = ...) -> None: ... + def validate(self, password: str, user: Optional[_UserModel] = ...) -> None: ... def get_help_text(self) -> str: ... class CommonPasswordValidator: DEFAULT_PASSWORD_LIST_PATH: Path = ... passwords: Set[str] = ... def __init__(self, password_list_path: Union[PosixPath, str] = ...) -> None: ... - def validate(self, password: str, user: Optional[AbstractBaseUser] = ...) -> None: ... + def validate(self, password: str, user: Optional[_UserModel] = ...) -> None: ... def get_help_text(self) -> str: ... class NumericPasswordValidator: - def validate(self, password: str, user: Optional[AbstractBaseUser] = ...) -> None: ... + def validate(self, password: str, user: Optional[_UserModel] = ...) -> None: ... def get_help_text(self) -> str: ... diff --git a/django-stubs/contrib/auth/urls.pyi b/django-stubs/contrib/auth/urls.pyi new file mode 100644 index 0000000..99cbb7c --- /dev/null +++ b/django-stubs/contrib/auth/urls.pyi @@ -0,0 +1,3 @@ +from typing import Any, List + +urlpatterns: List[Any] = ... diff --git a/django-stubs/core/mail/message.pyi b/django-stubs/core/mail/message.pyi index 720ac5f..172167a 100644 --- a/django-stubs/core/mail/message.pyi +++ b/django-stubs/core/mail/message.pyi @@ -71,7 +71,8 @@ class EmailMessage: reply_to: Optional[Union[List[Optional[str]], str]] = ..., ) -> None: ... def get_connection(self, fail_silently: bool = ...) -> Any: ... - def message(self) -> MIMEMixin: ... + # TODO: when typeshed gets more types for email.Message, move it to MIMEMessage, now it has too many false-positives + def message(self) -> Any: ... def recipients(self) -> List[str]: ... def send(self, fail_silently: bool = ...) -> int: ... def attach( diff --git a/django-stubs/core/management/commands/makemessages.pyi b/django-stubs/core/management/commands/makemessages.pyi new file mode 100644 index 0000000..9da96a2 --- /dev/null +++ b/django-stubs/core/management/commands/makemessages.pyi @@ -0,0 +1,45 @@ +import os +import re +from typing import Any, Pattern, Type, Optional + +from django.core.management.base import BaseCommand +from django.utils.functional import cached_property +from django.utils.jslex import prepare_js_for_gettext + +from django.conf import settings +from django.utils.translation import templatize + +plural_forms_re: Pattern = ... +STATUS_OK: int = ... +NO_LOCALE_DIR: Any = ... + +def check_programs(*programs: str) -> None: ... + +class TranslatableFile: + dirpath: str + file_name: str + locale_dir: str + def __init__(self, dirpath: str, file_name: str, locale_dir: Optional[str]) -> None: ... + +class BuildFile: + """ + Represent the state of a translatable file during the build process. + """ + + def __init__(self, command: BaseCommand, domain: str, translatable: TranslatableFile) -> None: ... + @property + def is_templatized(self) -> bool: ... + @property + def path(self) -> str: ... + @property + def work_path(self) -> str: ... + def preprocess(self) -> None: ... + def postprocess_messages(self, msgs: str) -> str: ... + def cleanup(self) -> None: ... + +def normalize_eols(raw_contents: str) -> str: ... +def write_pot_file(potfile: str, msgs: str) -> None: ... + +class Command(BaseCommand): + translatable_file_class: Type[TranslatableFile] = ... + build_file_class: Type[BuildFile] = ... diff --git a/django-stubs/db/models/base.pyi b/django-stubs/db/models/base.pyi index d60aa96..6c9aa45 100644 --- a/django-stubs/db/models/base.pyi +++ b/django-stubs/db/models/base.pyi @@ -1,6 +1,5 @@ from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, TypeVar, Union -from django.core import checks from django.db.models.manager import Manager from django.core.checks.messages import CheckMessage diff --git a/django-stubs/forms/forms.pyi b/django-stubs/forms/forms.pyi index 93b31bd..561e16a 100644 --- a/django-stubs/forms/forms.pyi +++ b/django-stubs/forms/forms.pyi @@ -1,4 +1,4 @@ -from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, Type, Union +from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, Type, Union, Tuple from django.core.exceptions import ValidationError as ValidationError from django.forms.boundfield import BoundField @@ -12,6 +12,8 @@ class DeclarativeFieldsMetaclass(MediaDefiningClass): def __new__(mcs, name: str, bases: Sequence[Type[BaseForm]], attrs: Dict[str, Any]) -> Type[BaseForm]: ... class BaseForm: + class Meta: + fields: Sequence[str] = ... default_renderer: Any = ... field_order: Any = ... use_required_attribute: bool = ... diff --git a/django-stubs/http/request.pyi b/django-stubs/http/request.pyi index e8d8471..035727a 100644 --- a/django-stubs/http/request.pyi +++ b/django-stubs/http/request.pyi @@ -1,5 +1,20 @@ from io import BytesIO -from typing import Any, BinaryIO, Dict, Iterable, List, Mapping, Optional, Pattern, Set, Tuple, Union, overload +from typing import ( + Any, + BinaryIO, + Dict, + Iterable, + List, + Mapping, + Optional, + Pattern, + Set, + Tuple, + Union, + overload, + TypeVar, + Type, +) from django.contrib.sessions.backends.base import SessionBase from django.utils.datastructures import CaseInsensitiveMapping, ImmutableList, MultiValueDict @@ -60,8 +75,10 @@ class HttpRequest(BytesIO): def body(self) -> bytes: ... def _load_post_and_files(self) -> None: ... +_Q = TypeVar("_Q", bound="QueryDict") + class QueryDict(MultiValueDict[str, str]): - encoding: Any = ... + encoding: str = ... _mutable: bool = ... def __init__( self, query_string: Optional[Union[str, bytes]] = ..., mutable: bool = ..., encoding: Optional[str] = ... @@ -70,6 +87,14 @@ class QueryDict(MultiValueDict[str, str]): def setlistdefault(self, key: str, default_list: Optional[List[str]] = ...) -> List[str]: ... def appendlist(self, key: str, value: str) -> None: ... def urlencode(self, safe: Optional[str] = ...) -> str: ... + @classmethod + def fromkeys( + cls: Type[_Q], + iterable: Iterable[Union[bytes, str]], + value: Any = ..., + mutable: bool = ..., + encoding: Optional[str] = ..., + ) -> _Q: ... @overload def bytes_to_text(s: bytes, encoding: str) -> str: ... diff --git a/django-stubs/utils/datastructures.pyi b/django-stubs/utils/datastructures.pyi index 5ce243c..bf62139 100644 --- a/django-stubs/utils/datastructures.pyi +++ b/django-stubs/utils/datastructures.pyi @@ -30,6 +30,8 @@ class OrderedSet(MutableSet[_K]): class MultiValueDictKeyError(KeyError): ... +_D = TypeVar("_D", bound="MultiValueDict") + class MultiValueDict(MutableMapping[_K, _V]): @overload def __init__(self, key_to_list_mapping: Mapping[_K, Optional[List[_V]]] = ...) -> None: ... @@ -41,7 +43,7 @@ class MultiValueDict(MutableMapping[_K, _V]): def appendlist(self, key: _K, value: _V) -> None: ... def lists(self) -> Iterable[Tuple[_K, List[_V]]]: ... def dict(self) -> Dict[_K, Union[_V, List[_V]]]: ... - def copy(self) -> MultiValueDict[_K, _V]: ... + def copy(self: _D) -> _D: ... # These overrides are needed to convince mypy that this isn't an abstract class def __delitem__(self, item: _K) -> None: ... def __getitem__(self, item: _K) -> Union[_V, Literal[[]]]: ... # type: ignore diff --git a/django-stubs/utils/translation/reloader.pyi b/django-stubs/utils/translation/reloader.pyi new file mode 100644 index 0000000..f105152 --- /dev/null +++ b/django-stubs/utils/translation/reloader.pyi @@ -0,0 +1,7 @@ +from pathlib import Path +from typing import Any, Optional + +from django.utils.autoreload import BaseReloader + +def watch_for_translation_changes(sender: BaseReloader, **kwargs: Any) -> None: ... +def translation_file_changed(sender: Optional[BaseReloader], file_path: Path, **kwargs: Any) -> bool: ... diff --git a/scripts/typecheck_tests.py b/scripts/typecheck_tests.py index 20b2ef6..583d1d2 100644 --- a/scripts/typecheck_tests.py +++ b/scripts/typecheck_tests.py @@ -87,6 +87,8 @@ IGNORED_ERRORS = { 'xml.dom', 'numpy', 'tblib', + 'bcrypt', + 'argon2', # TODO: values().annotate() 'TypedDict', 'namedtuple', @@ -138,7 +140,21 @@ IGNORED_ERRORS = { '"validate_password" does not return a value', '"password_changed" does not return a value', re.compile(r'"validate" of "([A-Za-z]+)" does not return a value'), - 'Module has no attribute "SessionStore"' + 'Module has no attribute "SessionStore"', + 'AbstractBaseUser', + 'Argument "user" to "password_changed" has incompatible type "object"', + 'Argument "password_validators" to "password_changed" has incompatible type "Tuple[Validator]"; ' + + 'expected "Optional[Sequence[PasswordValidator]]"', + 'Value of type "Optional[List[_Record]]" is not indexable', + '"Model" has no attribute', + 'Incompatible type for "id" of "User"', + re.compile(r'Module has no attribute "(update|revert)_proxy_model_permissions"'), + 'Unsupported right operand type for in ("object")', + 'mock_getpass', + 'Unsupported left operand type for + ("Sequence[str]")', + 'AuthenticationFormWithInactiveUsersOkay', + 'has no attribute "alternatives"', + 'Incompatible types in assignment (expression has type "Dict[str, Any]", variable has type "QueryDict")' ], 'basic': [ 'Unexpected keyword argument "unknown_kwarg" for "refresh_from_db" of "Model"', @@ -221,6 +237,9 @@ IGNORED_ERRORS = { 'expressions_window': [ 'has incompatible type "str"' ], + 'extra_regress': [ + '"User" has no attribute "alpha"' + ], 'file_uploads': [ '"Iterable[Any]" has no attribute', '"IO[Any]" has no attribute' @@ -285,12 +304,23 @@ IGNORED_ERRORS = { '"HasLinkThing" has no attribute', '"Link" has no attribute' ], + 'httpwrappers': [ + 'Argument 2 to "appendlist" of "QueryDict"', + 'Incompatible types in assignment (expression has type "int", target has type "Union[str, List[str]]")', + 'Argument 1 to "fromkeys" of "QueryDict" has incompatible type "int"' + ], 'humanize_tests': [ 'Argument 1 to "append" of "list" has incompatible type "None"; expected "str"' ], 'inline_formsets': [ 'has no attribute "form"' ], + 'i18n': [ + 'Incompatible types in assignment (expression has type "I18nForm", variable has type "SelectDateForm")', + 'has incompatible type "object"', + 'Value of type "Optional[List[_Record]]" is not indexable', + '"Command" has no attribute' + ], 'logging_tests': [ '"Handler" has no attribute "stream"' ], @@ -474,6 +504,9 @@ IGNORED_ERRORS = { 'Incompatible types in assignment (expression has type "Parent2", variable has type "Parent1")', 'has no attribute' ], + 'select_for_update': [ + 'has incompatible type "object"' + ], 'servers': [ re.compile('Argument [0-9] to "WSGIRequestHandler"'), '"HTTPResponse" has no attribute', @@ -564,7 +597,7 @@ TESTS_DIRS = [ 'annotations', 'app_loading', 'apps', - # TODO: 'auth_tests', + 'auth_tests', 'backends', 'base', 'bash_completion', @@ -604,7 +637,7 @@ TESTS_DIRS = [ 'expressions', 'expressions_case', 'expressions_window', - # TODO: 'extra_regress', + 'extra_regress', 'field_deconstruction', 'field_defaults', 'field_subclassing', @@ -629,9 +662,9 @@ TESTS_DIRS = [ 'get_or_create', # TODO: 'gis_tests', 'handlers', - # TODO: 'httpwrappers', + 'httpwrappers', 'humanize_tests', - # TODO: 'i18n', + 'i18n', 'import_error_package', 'indexes', 'inline_formsets', @@ -705,7 +738,7 @@ TESTS_DIRS = [ 'reverse_lookup', 'save_delete_hooks', 'schema', - # TODO: 'select_for_update', + 'select_for_update', 'select_related', 'select_related_onetoone', 'select_related_regress',