27 Commits

Author SHA1 Message Date
Maxim Kurnikov
540e28f4c6 bump version to 1.3.0 2019-12-06 23:37:19 +03:00
Maksim Kurnikov
4ac43c6ed6 Add Django 3.0 testing to CI (#246)
* add Django 3.0 testing to CI

* remove importlib_metadata usage

* conditionally load choices module for tests
2019-12-06 23:36:24 +03:00
Maksim Kurnikov
cadd6c963b fix model's on_cascade= parameter for test, update to latest gdal (#247) 2019-12-06 03:40:55 +03:00
Konstantin Alekseev
041754f817 Fix smtp backend open (#240) 2019-12-01 20:46:11 +03:00
Konstantin Alekseev
c0c5d1e588 Cleanup EmailMessage types (#208)
* Cleanup EmailMessage types

* Typecheck email module tests.
2019-12-01 17:14:16 +03:00
Maksim Kurnikov
f824003cc4 remove unused ignore pattern (#239) 2019-12-01 00:09:36 +03:00
Youssef Moussaoui
58f1833cab Declare is_relation and related_model on Field (#230) 2019-11-30 22:40:22 +03:00
Maksim Kurnikov
cbb6a7a9ac Merge pull request #238 from mkurnikov/mypy-750
Mypy 0.750 support
2019-11-30 22:27:50 +03:00
Maxim Kurnikov
2c4827bbaf properly change type of self for methods on custom manager classes 2019-11-30 22:08:16 +03:00
Maxim Kurnikov
5a151bf851 update django tests branch 2019-11-30 20:56:31 +03:00
Konstantin Alekseev
cbc7159995 Support mypy 0.750 2019-11-30 13:39:28 +03:00
Patrick Gingras
df4c17a947 added base_fields and declared_fields properties to Form (#235) 2019-11-27 22:48:05 +03:00
yaegassy
445abc046c README.md Fix: sample code for "Notes" (#234) 2019-11-26 15:40:21 +03:00
Anthony Ricaud
557b7a4fa3 Add new View.setup method introduced in Django 2.2 (#233)
https://docs.djangoproject.com/en/2.2/ref/class-based-views/base/#django.views.generic.base.View.setup
https://github.com/django/django/blob/2.2/django/views/generic/base.py#L83-L87
2019-11-20 23:49:38 +03:00
Maksim Kurnikov
8343d76895 Fix has_perm() methods for auth backend, and for contrib.auth.models (#232)
* fix has_perm() methods

* lint
2019-11-19 04:54:17 +03:00
Maksim Kurnikov
8d986a0f43 remove catch-all __getattr__ for Manager, fix some issues with manager methods (#227) 2019-11-12 20:36:07 +03:00
Pilifer
e9a90ebff0 More precise annotations of utils.timezone functions that return instances of tzinfo subclasses. (#209)
* fix annotations of utils.timezone

* use intermediary tzinfo subclass exposed in pytz typeshed

* fix annotations of get_fixed_timezone as it returns datetime.timezone in Django 2.2

* add explanatory comment to get_current_timezone annotations

* black utils.timezone.pyi
2019-11-12 18:27:54 +03:00
Christopher Sabater Cordero
7b74a6944a Add a few missing types to the stubfiles (#214)
* Add types to stub files.

* Fix black and flake8 errors.
2019-11-12 18:25:31 +03:00
Seth Yastrov
83f11a0fc6 Add Tags.translation to stub (#226) 2019-11-12 18:23:29 +03:00
Seth Yastrov
2829faf1af Both CSRF_COOKIE_SAMESITE and SESSION_COOKIE_SAMESITE should be Optional (#216) 2019-11-12 16:13:16 +03:00
src
d061e84cc7 Add HttpResponsePermanentRedirect to django.shortcuts (#211) 2019-11-12 16:12:36 +03:00
Anna Sidwell
3a9263dc62 Two small improvements (#217)
* Add return type for admin.SimpleListFilter.lookups

* force_login() can take any user, not just builtin
2019-11-12 13:31:24 +03:00
Yngve Høiseth
14aea2b4d4 Allow returning bool from test_func (#220) 2019-11-12 05:52:23 +03:00
Maksim Kurnikov
287c64d6fb Pin to 0.740 and fix CI (#225)
* update django sources

* pin mypy version, update to 0.740

* fix tests typechecking

* fix lint
2019-11-12 05:17:36 +03:00
Nikita Sobolev
6601121db2 Fixes travis url 2019-10-21 23:47:06 +03:00
Andrey
87d59c7c1a Fix django.contrib.admin.options.BaseModelAdmin.has_add_permission (#205)
* Fix `django.contrib.admin.options.BaseModelAdmin.has_add_permission` (#203).

* Add `django.db.models.base.Model.unique_error_message` (#204).
2019-10-11 12:16:21 +03:00
Maxim Kurnikov
8402e7c53e improve annotations in some places (#202)
* improve annotations in some places

* linting
2019-10-07 14:50:45 +03:00
77 changed files with 704 additions and 307 deletions

2
.gitignore vendored
View File

@@ -9,3 +9,5 @@ build/
dist/
pip-wheel-metadata/
.pytest_cache/
/.envrc
/.direnv

4
.gitmodules vendored
View File

@@ -1,4 +0,0 @@
[submodule "django-sources"]
path = django-sources
url = https://github.com/django/django.git
branch = stable/2.2.x

View File

@@ -8,13 +8,23 @@ jobs:
python: 3.7
script: 'pytest'
- name: Typecheck Django test suite with python 3.7
- name: Typecheck Django 3.0 test suite with python 3.7
python: 3.7
script: 'python ./scripts/typecheck_tests.py'
script: |
pip install Django==3.0.*
python ./scripts/typecheck_tests.py --django_version=3.0
- name: Typecheck Django test suite with python 3.6
- name: Typecheck Django 3.0 test suite with python 3.6
python: 3.6
script: 'python ./scripts/typecheck_tests.py'
script: |
pip install Django==3.0.*
python ./scripts/typecheck_tests.py --django_version=3.0
- name: Typecheck Django 2.2 test suite with python 3.7
python: 3.7
script: |
pip install Django==2.2.*
python ./scripts/typecheck_tests.py --django_version=2.2
- name: Mypy for plugin code
python: 3.7
@@ -37,8 +47,9 @@ jobs:
script: 'isort --check --diff'
before_install: |
sudo apt update
sudo apt install binutils libproj-dev gdal-bin
sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable -y
sudo apt-get update
sudo apt-get install -y binutils libproj-dev gdal-bin
pip install -U pip setuptools wheel
install: |
pip install -r ./dev-requirements.txt

View File

@@ -2,7 +2,7 @@
# pep484 stubs for Django framework
[![Build Status](https://travis-ci.org/typeddjango/django-stubs.svg?branch=master)](https://travis-ci.org/typeddjango/django-stubs)
[![Build Status](https://travis-ci.com/typeddjango/django-stubs.svg?branch=master)](https://travis-ci.com/typeddjango/django-stubs)
[![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
[![Gitter](https://badges.gitter.im/mypy-django/Lobby.svg)](https://gitter.im/mypy-django/Lobby)
@@ -22,6 +22,7 @@ pip install django-stubs
| django-stubs | mypy version | django version | python version
| ------------ | ---- | ---- | ---- |
| 1.3.0 | 0.750 | 2.2.x | ^3.6
| 1.2.0 | 0.730 | 2.2.x | ^3.6
| 1.1.0 | 0.720 | 2.2.x | ^3.6
| 0.12.x | old semantic analyzer (<0.711), dmypy support | 2.1.x | ^3.6
@@ -69,7 +70,7 @@ class MyUserManager(models.Manager['MyUser']):
pass
class MyUser(models.Model):
objects = UserManager()
objects = MyUserManager()
```
work, which should make a error messages a bit better.

View File

@@ -4,4 +4,5 @@ psycopg2
flake8==3.7.8
flake8-pyi==19.3.0
isort==4.3.21
gitpython==3.0.5
-e .

Submodule django-sources deleted from f452d4232e

View File

@@ -1,5 +1,6 @@
from typing import Any, Iterator, Type, Optional, Dict
from django.apps.registry import Apps
from django.db.models.base import Model
MODELS_MODULE_NAME: str
@@ -7,11 +8,11 @@ MODELS_MODULE_NAME: str
class AppConfig:
name: str = ...
module: Optional[Any] = ...
apps: None = ...
apps: Optional[Apps] = ...
label: str = ...
verbose_name: str = ...
path: str = ...
models_module: None = ...
models_module: Optional[str] = ...
models: Dict[str, Type[Model]] = ...
def __init__(self, app_name: str, app_module: Optional[Any]) -> None: ...
@classmethod

View File

@@ -345,7 +345,7 @@ SESSION_COOKIE_PATH = "/"
SESSION_COOKIE_HTTPONLY = True
# Whether to set the flag restricting cookie leaks on cross-site requests.
# This can be 'Lax', 'Strict', or None to disable the flag.
SESSION_COOKIE_SAMESITE = "Lax"
SESSION_COOKIE_SAMESITE: Optional[str] = ...
# Whether to save the session data on every request.
SESSION_SAVE_EVERY_REQUEST = False
# Whether a user's session cookie expires when the Web browser is closed.
@@ -413,7 +413,7 @@ CSRF_COOKIE_DOMAIN = None
CSRF_COOKIE_PATH = "/"
CSRF_COOKIE_SECURE = False
CSRF_COOKIE_HTTPONLY = False
CSRF_COOKIE_SAMESITE = "Lax"
CSRF_COOKIE_SAMESITE: Optional[str] = ...
CSRF_HEADER_NAME = "HTTP_X_CSRFTOKEN"
CSRF_TRUSTED_ORIGINS: List[str] = ...
CSRF_USE_SESSIONS = False

View File

@@ -24,7 +24,7 @@ class SimpleListFilter(ListFilter):
parameter_name: Any = ...
lookup_choices: Any = ...
def value(self) -> Optional[str]: ...
def lookups(self, request: Any, model_admin: Any) -> None: ...
def lookups(self, request: Any, model_admin: Any) -> List[Tuple[Any, str]]: ...
class FieldListFilter(ListFilter):
field: Field = ...

View File

@@ -86,7 +86,7 @@ class BaseModelAdmin:
def get_sortable_by(self, request: HttpRequest) -> Union[List[Callable], List[str], Tuple]: ...
def lookup_allowed(self, lookup: str, value: str) -> bool: ...
def to_field_allowed(self, request: HttpRequest, to_field: str) -> bool: ...
def has_add_permission(self, request: HttpRequest, obj: Optional[Model] = ...) -> bool: ...
def has_add_permission(self, request: HttpRequest) -> bool: ...
def has_change_permission(self, request: HttpRequest, obj: Optional[Model] = ...) -> bool: ...
def has_delete_permission(self, request: HttpRequest, obj: Optional[Model] = ...) -> bool: ...
def has_view_permission(self, request: HttpRequest, obj: Optional[Model] = ...) -> bool: ...

View File

@@ -27,7 +27,7 @@ class ResultList(list):
def results(cl: ChangeList) -> Iterator[ResultList]: ...
def result_hidden_fields(cl: ChangeList) -> Iterator[BoundField]: ...
def result_list(
cl: ChangeList
cl: ChangeList,
) -> Dict[
str, Union[List[Dict[str, Optional[Union[int, str]]]], List[ResultList], List[BoundField], ChangeList, int]
]: ...

View File

@@ -81,9 +81,7 @@ class ChangeList:
paginator: Any = ...
def get_results(self, request: WSGIRequest) -> None: ...
def get_ordering_field(self, field_name: Union[Callable, str]) -> Optional[Union[CombinedExpression, str]]: ...
def get_ordering(
self, request: WSGIRequest, queryset: QuerySet
) -> Union[List[Union[Combinable, str]], List[Union[OrderBy, str]]]: ...
def get_ordering(self, request: WSGIRequest, queryset: QuerySet) -> List[Union[OrderBy, Combinable, str]]: ...
def get_ordering_field_columns(self) -> OrderedDict: ...
def get_queryset(self, request: WSGIRequest) -> QuerySet: ...
def apply_select_related(self, qs: QuerySet) -> QuerySet: ...

View File

@@ -3,20 +3,22 @@ from typing import Any, Optional, Set, Union
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import AnonymousUser, User
from django.db.models.base import Model
_AnyUser = Union[Model, AnonymousUser]
UserModel: Any
class ModelBackend:
def authenticate(
self, request: Any, username: Optional[Union[int, str]] = ..., password: Optional[str] = ..., **kwargs: Any
self, request: Any, username: Optional[str] = ..., password: Optional[str] = ..., **kwargs: Any
) -> Optional[AbstractBaseUser]: ...
def user_can_authenticate(self, user: Optional[AbstractBaseUser]) -> bool: ...
def get_user_permissions(self, user_obj: AbstractBaseUser, obj: None = ...) -> Set[str]: ...
def get_group_permissions(self, user_obj: AbstractBaseUser, obj: None = ...) -> Set[str]: ...
def get_all_permissions(self, user_obj: AbstractBaseUser, obj: Optional[str] = ...) -> Set[str]: ...
def has_perm(
self, user_obj: Union[AbstractBaseUser, AnonymousUser], perm: str, obj: Optional[str] = ...
) -> bool: ...
def has_module_perms(self, user_obj: Union[AbstractBaseUser, AnonymousUser], app_label: str) -> bool: ...
def user_can_authenticate(self, user: Optional[_AnyUser]) -> bool: ...
def get_user_permissions(self, user_obj: _AnyUser, obj: Optional[Model] = ...) -> Set[str]: ...
def get_group_permissions(self, user_obj: _AnyUser, obj: Optional[Model] = ...) -> Set[str]: ...
def get_all_permissions(self, user_obj: _AnyUser, obj: Optional[Model] = ...) -> Set[str]: ...
def has_perm(self, user_obj: _AnyUser, perm: str, obj: Optional[Model] = ...) -> bool: ...
def has_module_perms(self, user_obj: _AnyUser, app_label: str) -> bool: ...
def get_user(self, user_id: int) -> AbstractBaseUser: ...
class AllowAllUsersModelBackend(ModelBackend): ...

View File

@@ -1,4 +1,4 @@
from typing import Any, Callable, List
from typing import Any, Callable, List, Optional
from django import http
from django.http.response import HttpResponse, HttpResponseRedirect
@@ -23,6 +23,6 @@ class PermissionRequiredMixin(AccessMixin):
def dispatch(self, request: http.HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ...
class UserPassesTestMixin(AccessMixin):
def test_func(self) -> None: ...
def test_func(self) -> Optional[bool]: ...
def get_test_func(self) -> Callable: ...
def dispatch(self, request: http.HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ...

View File

@@ -1,4 +1,4 @@
from typing import Any, Collection, Optional, Set, Tuple, Type, TypeVar
from typing import Any, Collection, Optional, Set, Tuple, Type, TypeVar, Union
from django.contrib.auth.base_user import AbstractBaseUser as AbstractBaseUser, BaseUserManager as BaseUserManager
from django.contrib.auth.validators import UnicodeUsernameValidator
@@ -8,22 +8,28 @@ from django.db.models.manager import EmptyManager
from django.db import models
_AnyUser = Union[Model, "AnonymousUser"]
def update_last_login(sender: Type[AbstractBaseUser], user: AbstractBaseUser, **kwargs: Any) -> None: ...
class PermissionManager(models.Manager):
class PermissionManager(models.Manager["Permission"]):
def get_by_natural_key(self, codename: str, app_label: str, model: str) -> Permission: ...
class Permission(models.Model):
content_type_id: int
objects: PermissionManager
name = models.CharField(max_length=255)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
codename = models.CharField(max_length=100)
def natural_key(self) -> Tuple[str, str, str]: ...
class GroupManager(models.Manager):
class GroupManager(models.Manager["Group"]):
def get_by_natural_key(self, name: str) -> Group: ...
class Group(models.Model):
objects: GroupManager
name = models.CharField(max_length=150)
permissions = models.ManyToManyField(Permission)
def natural_key(self): ...
@@ -40,12 +46,12 @@ class UserManager(BaseUserManager[_T]):
class PermissionsMixin(models.Model):
is_superuser = models.BooleanField()
groups: models.ManyToManyField = models.ManyToManyField(Group)
user_permissions: models.ManyToManyField = models.ManyToManyField(Permission)
def get_group_permissions(self, obj: None = ...) -> Set[str]: ...
def get_all_permissions(self, obj: Optional[str] = ...) -> Set[str]: ...
def has_perm(self, perm: str, obj: Optional[str] = ...) -> bool: ...
def has_perms(self, perm_list: Collection[str], obj: None = ...) -> bool: ...
groups = models.ManyToManyField(Group)
user_permissions = models.ManyToManyField(Permission)
def get_group_permissions(self, obj: Optional[_AnyUser] = ...) -> Set[str]: ...
def get_all_permissions(self, obj: Optional[_AnyUser] = ...) -> Set[str]: ...
def has_perm(self, perm: str, obj: Optional[_AnyUser] = ...) -> bool: ...
def has_perms(self, perm_list: Collection[str], obj: Optional[_AnyUser] = ...) -> bool: ...
def has_module_perms(self, app_label: str) -> bool: ...
class AbstractUser(AbstractBaseUser, PermissionsMixin): # type: ignore
@@ -82,10 +88,10 @@ class AnonymousUser:
def groups(self) -> EmptyManager: ...
@property
def user_permissions(self) -> EmptyManager: ...
def get_group_permissions(self, obj: None = ...) -> Set[Any]: ...
def get_all_permissions(self, obj: Any = ...) -> Set[str]: ...
def has_perm(self, perm: str, obj: None = ...) -> bool: ...
def has_perms(self, perm_list: Collection[str], obj: None = ...) -> bool: ...
def get_group_permissions(self, obj: Optional[_AnyUser] = ...) -> Set[Any]: ...
def get_all_permissions(self, obj: Optional[_AnyUser] = ...) -> Set[str]: ...
def has_perm(self, perm: str, obj: Optional[_AnyUser] = ...) -> bool: ...
def has_perms(self, perm_list: Collection[str], obj: Optional[_AnyUser] = ...) -> bool: ...
def has_module_perms(self, module: str) -> bool: ...
@property
def is_anonymous(self) -> bool: ...

View File

@@ -6,15 +6,16 @@ from django.db import models
SITE_CACHE: Any
class SiteManager(models.Manager):
class SiteManager(models.Manager["Site"]):
def get_current(self, request: Optional[HttpRequest] = ...) -> Site: ...
def clear_cache(self) -> None: ...
def get_by_natural_key(self, domain: str) -> Site: ...
class Site(models.Model):
domain: models.CharField = ...
name: models.CharField = ...
objects: SiteManager = ...
objects: SiteManager
domain = models.CharField(max_length=100)
name = models.CharField(max_length=50)
def natural_key(self) -> Tuple[str]: ...
def clear_site_cache(sender: Type[Site], **kwargs: Any) -> None: ...

View File

@@ -37,7 +37,7 @@ def get_finders() -> Iterator[BaseFinder]: ...
def get_finder(import_path: Literal["django.contrib.staticfiles.finders.FileSystemFinder"]) -> FileSystemFinder: ...
@overload
def get_finder(
import_path: Literal["django.contrib.staticfiles.finders.AppDirectoriesFinder"]
import_path: Literal["django.contrib.staticfiles.finders.AppDirectoriesFinder"],
) -> AppDirectoriesFinder: ...
@overload
def get_finder(import_path: str) -> BaseFinder: ...

View File

@@ -12,6 +12,7 @@ class Tags:
security: str = ...
signals: str = ...
templates: str = ...
translation: str = ...
urls: str = ...
class CheckRegistry:

View File

@@ -1,5 +1,5 @@
import types
from typing import Any, TypeVar, Type, Iterable
from typing import Any, TypeVar, Type, Iterable, Optional
from django.core.mail.message import EmailMessage
@@ -7,7 +7,7 @@ _T = TypeVar("_T", bound="BaseEmailBackend")
class BaseEmailBackend:
def __init__(self, fail_silently: bool = ..., **kwargs: Any) -> None: ...
def open(self) -> bool: ...
def open(self) -> Optional[bool]: ...
def close(self) -> None: ...
def __enter__(self: _T) -> _T: ...
def __exit__(

View File

@@ -0,0 +1,3 @@
from django.core.mail.backends.base import BaseEmailBackend
class EmailBackend(BaseEmailBackend): ...

View File

@@ -0,0 +1,3 @@
from django.core.mail.backends.base import BaseEmailBackend
class EmailBackend(BaseEmailBackend): ...

View File

@@ -0,0 +1,3 @@
from django.core.mail.backends.base import BaseEmailBackend
class EmailBackend(BaseEmailBackend): ...

View File

@@ -0,0 +1,3 @@
from django.core.mail.backends.base import BaseEmailBackend
class EmailBackend(BaseEmailBackend): ...

View File

@@ -0,0 +1,18 @@
import smtplib
import threading
from typing import Optional, Union
from django.core.mail.backends.base import BaseEmailBackend
class EmailBackend(BaseEmailBackend):
host: str = ...
port: int = ...
username: str = ...
password: str = ...
use_tls: bool = ...
use_ssl: bool = ...
timeout: Optional[int] = ...
ssl_keyfile: Optional[str] = ...
ssl_certfile: Optional[str] = ...
connection: Union[smtplib.SMTP_SSL, smtplib.SMTP, None] = ...
_lock: threading.RLock = ...

View File

@@ -1,8 +1,10 @@
from email._policybase import Policy # type: ignore
from email.message import Message
from email.mime.base import MIMEBase
from email.mime.message import MIMEMessage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, overload
utf8_charset: Any
utf8_charset_qp: Any
@@ -43,6 +45,11 @@ class SafeMIMEMultipart(MIMEMixin, MIMEMultipart):
self, _subtype: str = ..., boundary: None = ..., _subparts: None = ..., encoding: str = ..., **_params: Any
) -> None: ...
_AttachmentContent = Union[bytes, EmailMessage, Message, SafeMIMEText, str]
_AttachmentTuple = Union[
Tuple[str, _AttachmentContent], Tuple[Optional[str], _AttachmentContent, str], Tuple[str, _AttachmentContent, None]
]
class EmailMessage:
content_subtype: str = ...
mixed_subtype: str = ...
@@ -62,42 +69,42 @@ class EmailMessage:
subject: str = ...,
body: Optional[str] = ...,
from_email: Optional[str] = ...,
to: Optional[Union[Sequence[str], str]] = ...,
bcc: Optional[Union[Sequence[str], str]] = ...,
to: Optional[Sequence[str]] = ...,
bcc: Optional[Sequence[str]] = ...,
connection: Optional[Any] = ...,
attachments: Optional[Union[List[Tuple[str, Union[str, bytes], str]], List[MIMEText]]] = ...,
attachments: Optional[Sequence[Union[MIMEBase, _AttachmentTuple]]] = ...,
headers: Optional[Dict[str, str]] = ...,
cc: Optional[Union[Sequence[str], str]] = ...,
reply_to: Optional[Union[List[Optional[str]], str]] = ...,
cc: Optional[Sequence[str]] = ...,
reply_to: Optional[Sequence[str]] = ...,
) -> None: ...
def get_connection(self, fail_silently: bool = ...) -> Any: ...
# 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(
self,
filename: Optional[Union[MIMEText, str]] = ...,
content: Optional[Union[bytes, EmailMessage, SafeMIMEText, str]] = ...,
mimetype: Optional[str] = ...,
) -> None: ...
@overload
def attach(self, filename: MIMEText = ...) -> None: ...
@overload
def attach(self, filename: None = ..., content: _AttachmentContent = ..., mimetype: str = ...) -> None: ...
@overload
def attach(self, filename: str = ..., content: _AttachmentContent = ..., mimetype: Optional[str] = ...) -> None: ...
def attach_file(self, path: str, mimetype: Optional[str] = ...) -> None: ...
class EmailMultiAlternatives(EmailMessage):
alternative_subtype: str = ...
alternatives: Any = ...
alternatives: Sequence[Tuple[_AttachmentContent, str]] = ...
def __init__(
self,
subject: str = ...,
body: str = ...,
from_email: Optional[str] = ...,
to: Optional[List[str]] = ...,
bcc: Optional[List[str]] = ...,
to: Optional[Sequence[str]] = ...,
bcc: Optional[Sequence[str]] = ...,
connection: Optional[Any] = ...,
attachments: None = ...,
attachments: Optional[Sequence[Union[MIMEBase, _AttachmentTuple]]] = ...,
headers: Optional[Dict[str, str]] = ...,
alternatives: Optional[List[Tuple[str, str]]] = ...,
cc: None = ...,
reply_to: None = ...,
alternatives: Optional[Sequence[Tuple[_AttachmentContent, str]]] = ...,
cc: Optional[Sequence[str]] = ...,
reply_to: Optional[Sequence[str]] = ...,
) -> None: ...
def attach_alternative(self, content: str, mimetype: str) -> None: ...
def attach_alternative(self, content: _AttachmentContent, mimetype: str) -> None: ...

View File

@@ -20,7 +20,7 @@ class BaseDatabaseWrapper:
ops: Any = ...
vendor: str = ...
display_name: str = ...
SchemaEditorClass: Any = ...
SchemaEditorClass: Optional[BaseDatabaseSchemaEditor] = ...
client_class: Any = ...
creation_class: Any = ...
features_class: Any = ...

View File

@@ -1,5 +1,5 @@
from collections import namedtuple
from typing import Any, Dict, List, Optional, Set, Tuple, Type, Union
from typing import Any, Dict, List, Optional, Set, Type
from django.db.backends.base.base import BaseDatabaseWrapper
from django.db.backends.utils import CursorWrapper
@@ -13,7 +13,7 @@ class BaseDatabaseIntrospection:
data_types_reverse: Any = ...
connection: Any = ...
def __init__(self, connection: BaseDatabaseWrapper) -> None: ...
def get_field_type(self, data_type: str, description: FieldInfo) -> Union[Tuple[str, Dict[str, int]], str]: ...
def get_field_type(self, data_type: str, description: FieldInfo) -> str: ...
def table_name_converter(self, name: str) -> str: ...
def column_name_converter(self, name: str) -> str: ...
def table_names(self, cursor: Optional[CursorWrapper] = ..., include_views: bool = ...) -> List[str]: ...

View File

@@ -1,10 +1,9 @@
from datetime import date, datetime, timedelta
from datetime import date, datetime, timedelta, time
from decimal import Decimal
from typing import Any, List, Optional, Sequence, Tuple, Type, Union
from django.core.management.color import Style
from django.db.backends.base.base import BaseDatabaseWrapper
from django.db.backends.sqlite3.base import DatabaseWrapper
from django.db.backends.utils import CursorWrapper
from django.db.models.base import Model
from django.db.models.expressions import Case, Expression
@@ -13,6 +12,8 @@ from django.db.models.sql.compiler import SQLCompiler
from django.db import DefaultConnectionProxy
from django.db.models.fields import Field
_Connection = Union[DefaultConnectionProxy, BaseDatabaseWrapper]
class BaseDatabaseOperations:
compiler_module: str = ...
integer_field_ranges: Any = ...
@@ -25,8 +26,8 @@ class BaseDatabaseOperations:
UNBOUNDED_FOLLOWING: Any = ...
CURRENT_ROW: str = ...
explain_prefix: Any = ...
connection: Any = ...
def __init__(self, connection: Optional[Union[DefaultConnectionProxy, BaseDatabaseWrapper]]) -> None: ...
connection: _Connection = ...
def __init__(self, connection: Optional[_Connection]) -> None: ...
def autoinc_sql(self, table: str, column: str) -> None: ...
def bulk_batch_size(self, fields: Any, objs: Any): ...
def cache_key_culling_sql(self) -> str: ...
@@ -75,10 +76,10 @@ class BaseDatabaseOperations:
def prep_for_like_query(self, x: str) -> str: ...
prep_for_iexact_query: Any = ...
def validate_autopk_value(self, value: int) -> int: ...
def adapt_unknown_value(self, value: Union[datetime, Decimal, int, str]) -> Union[int, str]: ...
def adapt_unknown_value(self, value: Any) -> Any: ...
def adapt_datefield_value(self, value: Optional[date]) -> Optional[str]: ...
def adapt_datetimefield_value(self, value: None) -> None: ...
def adapt_timefield_value(self, value: Optional[datetime]) -> Optional[str]: ...
def adapt_datetimefield_value(self, value: Optional[datetime]) -> Optional[str]: ...
def adapt_timefield_value(self, value: Optional[Union[datetime, time]]) -> Optional[str]: ...
def adapt_decimalfield_value(
self, value: Optional[Decimal], max_digits: Optional[int] = ..., decimal_places: Optional[int] = ...
) -> Optional[str]: ...
@@ -87,19 +88,17 @@ class BaseDatabaseOperations:
def year_lookup_bounds_for_datetime_field(self, value: int) -> List[str]: ...
def get_db_converters(self, expression: Expression) -> List[Any]: ...
def convert_durationfield_value(
self, value: Optional[float], expression: Expression, connection: DatabaseWrapper
self, value: Optional[float], expression: Expression, connection: _Connection
) -> Optional[timedelta]: ...
def check_expression_support(self, expression: Any) -> None: ...
def combine_expression(self, connector: str, sub_expressions: List[str]) -> str: ...
def combine_duration_expression(self, connector: Any, sub_expressions: Any): ...
def binary_placeholder_sql(self, value: Optional[Case]) -> str: ...
def modify_insert_params(
self, placeholder: str, params: Union[List[None], List[bool], List[float], List[str]]
) -> Union[List[None], List[bool], List[float], List[str]]: ...
def modify_insert_params(self, placeholder: str, params: Any) -> Any: ...
def integer_field_range(self, internal_type: Any): ...
def subtract_temporals(self, internal_type: Any, lhs: Any, rhs: Any): ...
def window_frame_start(self, start: Any): ...
def window_frame_end(self, end: Any): ...
def window_frame_rows_start_end(self, start: None = ..., end: None = ...) -> Any: ...
def window_frame_range_start_end(self, start: Optional[Any] = ..., end: Optional[Any] = ...): ...
def window_frame_rows_start_end(self, start: Optional[int] = ..., end: Optional[int] = ...) -> Any: ...
def window_frame_range_start_end(self, start: Optional[int] = ..., end: Optional[int] = ...) -> Any: ...
def explain_query_prefix(self, format: Optional[str] = ..., **options: Any) -> str: ...

View File

@@ -48,7 +48,7 @@ class BaseDatabaseSchemaEditor(ContextManager[Any]):
def quote_name(self, name: str) -> str: ...
def column_sql(
self, model: Type[Model], field: Field, include_default: bool = ...
) -> Union[Tuple[None, None], Tuple[str, List[Any]]]: ...
) -> Tuple[Optional[str], Optional[List[Any]]]: ...
def skip_default(self, field: Any): ...
def prepare_default(self, value: Any) -> None: ...
def effective_default(self, field: Field) -> Optional[Union[int, str]]: ...

View File

@@ -1,4 +1,4 @@
from typing import Any, Dict, Optional, Tuple, Union
from typing import Any, Optional
from django.db.backends.base.introspection import BaseDatabaseIntrospection
@@ -8,6 +8,6 @@ def get_field_size(name: str) -> Optional[int]: ...
class FlexibleFieldLookupDict:
base_data_types_reverse: Any = ...
def __getitem__(self, key: str) -> Union[Tuple[str, Dict[str, int]], str]: ...
def __getitem__(self, key: str) -> Any: ...
class DatabaseIntrospection(BaseDatabaseIntrospection): ...

View File

@@ -40,6 +40,7 @@ from .fields import (
DurationField as DurationField,
BigAutoField as BigAutoField,
CommaSeparatedIntegerField as CommaSeparatedIntegerField,
NOT_PROVIDED as NOT_PROVIDED,
)
from .fields.related import (
@@ -128,3 +129,5 @@ from .constraints import (
CheckConstraint as CheckConstraint,
UniqueConstraint as UniqueConstraint,
)
from .enums import Choices as Choices, IntegerChoices as IntegerChoices, TextChoices as TextChoices

View File

@@ -1,6 +1,7 @@
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, TypeVar, Union
from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union, Collection
from django.core.checks.messages import CheckMessage
from django.core.exceptions import ValidationError
from django.db.models.manager import Manager
from django.db.models.options import Options
@@ -18,10 +19,13 @@ class Model(metaclass=ModelBase):
pk: Any = ...
def __init__(self: _Self, *args, **kwargs) -> None: ...
def delete(self, using: Any = ..., keep_parents: bool = ...) -> Tuple[int, Dict[str, int]]: ...
def full_clean(self, exclude: Optional[List[str]] = ..., validate_unique: bool = ...) -> None: ...
def full_clean(self, exclude: Optional[Collection[str]] = ..., validate_unique: bool = ...) -> None: ...
def clean(self) -> None: ...
def clean_fields(self, exclude: List[str] = ...) -> None: ...
def validate_unique(self, exclude: List[str] = ...) -> None: ...
def clean_fields(self, exclude: Optional[Collection[str]] = ...) -> None: ...
def validate_unique(self, exclude: Optional[Collection[str]] = ...) -> None: ...
def unique_error_message(
self, model_class: Type[_Self], unique_check: Collection[Union[Callable, str]]
) -> ValidationError: ...
def save(
self,
force_insert: bool = ...,

View File

@@ -0,0 +1,30 @@
import enum
from typing import Any, List, Tuple
class ChoicesMeta(enum.EnumMeta):
names: List[str] = ...
choices: List[Tuple[Any, str]] = ...
labels: List[str] = ...
values: List[Any] = ...
def __contains__(self, item: Any) -> bool: ...
class Choices(enum.Enum, metaclass=ChoicesMeta):
def __str__(self): ...
# fake
class _IntegerChoicesMeta(ChoicesMeta):
names: List[str] = ...
choices: List[Tuple[int, str]] = ...
labels: List[str] = ...
values: List[int] = ...
class IntegerChoices(int, Choices, metaclass=_IntegerChoicesMeta): ...
# fake
class _TextChoicesMeta(ChoicesMeta):
names: List[str] = ...
choices: List[Tuple[str, str]] = ...
labels: List[str] = ...
values: List[str] = ...
class TextChoices(str, Choices, metaclass=_TextChoicesMeta): ...

View File

@@ -25,7 +25,7 @@ from django.db.models.expressions import Combinable, Col
from django.db.models.query_utils import RegisterLookupMixin
from django.forms import Field as FormField, Widget
from .mixins import NOT_PROVIDED as NOT_PROVIDED
class NOT_PROVIDED: ...
_Choice = Tuple[Any, Any]
_ChoiceNamedGroup = Tuple[str, Iterable[_Choice]]
@@ -52,6 +52,8 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
auto_created: bool
primary_key: bool
remote_field: Field
is_relation: bool
related_model: Optional[Type[Model]]
max_length: int
model: Type[Model]
name: str
@@ -64,6 +66,7 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
choices: Optional[_FieldChoices] = ...
db_column: Optional[str]
column: str
default: Any
error_messages: _ErrorMessagesToOverride
def __init__(
self,
@@ -105,7 +108,8 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
def db_parameters(self, connection: Any) -> Dict[str, str]: ...
def get_prep_value(self, value: Any) -> Any: ...
def get_internal_type(self) -> str: ...
def formfield(self, **kwargs) -> FormField: ...
# TODO: plugin support
def formfield(self, **kwargs) -> Any: ...
def save_form_data(self, instance: Model, data: Any) -> None: ...
def contribute_to_class(self, cls: Type[Model], name: str, private_only: bool = ...) -> None: ...
def to_python(self, value: Any) -> Any: ...
@@ -128,11 +132,12 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
@property
def cached_col(self) -> Col: ...
def value_from_object(self, obj: Model) -> _GT: ...
def get_attname(self) -> str: ...
class IntegerField(Field[_ST, _GT]):
_pyi_private_set_type: Union[float, int, str, Combinable]
_pyi_private_get_type: int
_pyi_lookup_exact_type: int
_pyi_lookup_exact_type: Union[str, int]
class PositiveIntegerRelDbTypeMixin:
def rel_db_type(self, connection: Any): ...
@@ -180,7 +185,7 @@ class DecimalField(Field[_ST, _GT]):
class AutoField(Field[_ST, _GT]):
_pyi_private_set_type: Union[Combinable, int, str]
_pyi_private_get_type: int
_pyi_lookup_exact_type: int
_pyi_lookup_exact_type: Union[str, int]
class CharField(Field[_ST, _GT]):
_pyi_private_set_type: Union[str, int, Combinable]
@@ -357,20 +362,20 @@ class UUIDField(Field[_ST, _GT]):
_pyi_private_get_type: uuid.UUID
class FilePathField(Field[_ST, _GT]):
path: str = ...
match: Optional[Any] = ...
path: Any = ...
match: Optional[str] = ...
recursive: bool = ...
allow_files: bool = ...
allow_folders: bool = ...
def __init__(
self,
verbose_name: Optional[Union[str, bytes]] = ...,
name: Optional[str] = ...,
path: str = ...,
match: Optional[Any] = ...,
path: Union[str, Callable[..., str]] = ...,
match: Optional[str] = ...,
recursive: bool = ...,
allow_files: bool = ...,
allow_folders: bool = ...,
verbose_name: Optional[str] = ...,
name: Optional[str] = ...,
primary_key: bool = ...,
max_length: int = ...,
unique: bool = ...,
@@ -389,7 +394,8 @@ class FilePathField(Field[_ST, _GT]):
error_messages: Optional[_ErrorMessagesToOverride] = ...,
): ...
class BinaryField(Field[_ST, _GT]): ...
class BinaryField(Field[_ST, _GT]):
_pyi_private_get_type: bytes
class DurationField(Field[_ST, _GT]):
_pyi_private_get_type: timedelta

View File

@@ -1,3 +1,4 @@
from pathlib import Path
from typing import Any, Callable, Iterable, List, Optional, Tuple, Type, TypeVar, Union, overload
from django.core.files.base import File
@@ -39,11 +40,10 @@ class FileField(Field):
upload_to: Union[str, Callable] = ...
def __init__(
self,
upload_to: Union[str, Callable, Path] = ...,
storage: Optional[Storage] = ...,
verbose_name: Optional[Union[str, bytes]] = ...,
name: Optional[str] = ...,
upload_to: Union[str, Callable] = ...,
storage: Optional[Storage] = ...,
primary_key: bool = ...,
max_length: Optional[int] = ...,
unique: bool = ...,
blank: bool = ...,

View File

@@ -43,9 +43,8 @@ class RelatedField(FieldCacheMixin, Field[_ST, _GT]):
one_to_one: bool = ...
many_to_many: bool = ...
many_to_one: bool = ...
related_model: Type[Model]
opts: Any = ...
@property
def related_model(self) -> Type[Model]: ...
def get_forward_related_filter(self, obj: Model) -> Dict[str, Union[int, UUID]]: ...
def get_reverse_related_filter(self, obj: Model) -> Q: ...
@property

View File

@@ -18,6 +18,7 @@ class ForeignObjectRel(FieldCacheMixin):
concrete: bool = ...
editable: bool = ...
is_relation: bool = ...
related_model: Type[Model]
null: bool = ...
field: RelatedField = ...
model: Union[Type[Model], str] = ...
@@ -44,8 +45,6 @@ class ForeignObjectRel(FieldCacheMixin):
@property
def name(self) -> str: ...
@property
def related_model(self) -> Type[Model]: ...
@property
def remote_field(self) -> RelatedField: ...
@property
def target_field(self) -> AutoField: ...

View File

@@ -31,11 +31,9 @@ class Lookup(Generic[_T]):
def process_lhs(
self, compiler: SQLCompiler, connection: DatabaseWrapper, lhs: Optional[Expression] = ...
) -> Tuple[str, List[Union[int, str]]]: ...
def process_rhs(
self, compiler: SQLCompiler, connection: DatabaseWrapper
) -> Tuple[str, Union[List[Union[int, str]], Tuple[int, int]]]: ...
def process_rhs(self, compiler: SQLCompiler, connection: DatabaseWrapper) -> Tuple[str, List[Union[int, str]]]: ...
def rhs_is_direct_value(self) -> bool: ...
def relabeled_clone(self, relabels: Mapping[str, str]) -> Union[BuiltinLookup, FieldGetDbPrepValueMixin]: ...
def relabeled_clone(self: _T, relabels: Mapping[str, str]) -> _T: ...
def get_group_by_cols(self) -> List[Expression]: ...
def as_sql(self, compiler: Any, connection: Any) -> Any: ...
def contains_aggregate(self) -> bool: ...

View File

@@ -1,16 +1,17 @@
from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar
from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar, Iterable, Union
from django.db.models.base import Model
from django.db.models.query import QuerySet
_T = TypeVar("_T", bound=Model, covariant=True)
_M = TypeVar("_M", bound="BaseManager")
class BaseManager(QuerySet[_T]):
creation_counter: int = ...
auto_created: bool = ...
use_in_migrations: bool = ...
name: str = ...
model: Type[Model] = ...
model: Type[_T] = ...
db: str
def __init__(self) -> None: ...
def deconstruct(self) -> Tuple[bool, str, None, Tuple, Dict[str, int]]: ...
@@ -20,13 +21,18 @@ class BaseManager(QuerySet[_T]):
@classmethod
def _get_queryset_methods(cls, queryset_class: type) -> Dict[str, Any]: ...
def contribute_to_class(self, model: Type[Model], name: str) -> None: ...
def db_manager(self, using: Optional[str] = ..., hints: Optional[Dict[str, Model]] = ...) -> Manager: ...
def db_manager(self: _M, using: Optional[str] = ..., hints: Optional[Dict[str, Model]] = ...) -> _M: ...
def get_queryset(self) -> QuerySet[_T]: ...
class Manager(BaseManager[_T]): ...
class RelatedManager(Manager[_T]):
def add(self, *objs: Model, bulk: bool = ...) -> None: ...
related_val: Tuple[int, ...]
def add(self, *objs: Union[_T, int], bulk: bool = ...) -> None: ...
def remove(self, *objs: Union[_T, int], bulk: bool = ...) -> None: ...
def set(
self, objs: Union[QuerySet[_T], Iterable[Union[_T, int]]], *, bulk: bool = ..., clear: bool = ...
) -> None: ...
def clear(self) -> None: ...
class ManagerDescriptor:

View File

@@ -1,5 +1,5 @@
import collections
from typing import Any, Callable, Dict, Generic, Iterator, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union
from typing import Any, Callable, Dict, Generic, Iterator, List, Optional, Sequence, Tuple, Type, TypeVar, Union
from django.apps.config import AppConfig
from django.apps.registry import Apps
@@ -23,8 +23,8 @@ IMMUTABLE_WARNING: str
DEFAULT_NAMES: Tuple[str, ...]
def normalize_together(
option_together: Any
) -> Union[List[Union[Tuple[str, str], int]], Set[Tuple[str, str]], Tuple, int, str]: ...
option_together: Union[Sequence[Tuple[str, str]], Tuple[str, str]]
) -> Tuple[Tuple[str, str], ...]: ...
def make_immutable_fields_list(
name: str, data: Union[Iterator[Any], List[Union[ArrayField, CIText]], List[Union[Field, FieldCacheMixin]]]
) -> ImmutableList: ...
@@ -108,6 +108,8 @@ class Options(Generic[_M]):
def managers(self) -> List[Manager]: ...
@property
def managers_map(self) -> Dict[str, Manager]: ...
@property
def db_returning_fields(self) -> List[Field]: ...
def get_field(self, field_name: Union[Callable, str]) -> Field: ...
def get_base_chain(self, model: Type[Model]) -> List[Type[Model]]: ...
def get_parent_list(self) -> List[Type[Model]]: ...

View File

@@ -52,8 +52,9 @@ class _BaseQuerySet(Generic[_T], Sized):
def get(self, *args: Any, **kwargs: Any) -> _T: ...
def create(self, *args: Any, **kwargs: Any) -> _T: ...
def bulk_create(
self, objs: Iterable[Model], batch_size: Optional[int] = ..., ignore_conflicts: bool = ...
self, objs: Iterable[_T], batch_size: Optional[int] = ..., ignore_conflicts: bool = ...
) -> List[_T]: ...
def bulk_update(self, objs: Iterable[_T], fields: Sequence[str], batch_size: Optional[int] = ...) -> None: ...
def get_or_create(self, defaults: Optional[MutableMapping[str, Any]] = ..., **kwargs: Any) -> Tuple[_T, bool]: ...
def update_or_create(
self, defaults: Optional[MutableMapping[str, Any]] = ..., **kwargs: Any
@@ -119,8 +120,6 @@ class _BaseQuerySet(Generic[_T], Sized):
@property
def db(self) -> str: ...
def resolve_expression(self, *args: Any, **kwargs: Any) -> Any: ...
# TODO: remove when django adds __class_getitem__ methods
def __getattr__(self, item: str) -> Any: ...
class QuerySet(_BaseQuerySet[_T], Collection[_T], Sized):
def __iter__(self) -> Iterator[_T]: ...

View File

@@ -1,13 +1,13 @@
from collections import OrderedDict, namedtuple
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Type, Union
from collections import namedtuple
from typing import Any, Collection, Dict, Iterator, List, Mapping, Optional, Sequence, Set, Tuple, Type
from django.db.models.base import Model
from django.db.models.expressions import Expression
from django.db.models.fields import Field
from django.db.models.fields.mixins import FieldCacheMixin
from django.db.models.sql.compiler import SQLCompiler
from django.db.models.sql.query import Query
from django.db.models.sql.where import WhereNode
from django.db.models.fields import Field
from django.utils import tree
PathInfo = namedtuple("PathInfo", "from_opts to_opts target_fields join_field m2m direct filtered_relation")
@@ -23,12 +23,8 @@ class QueryWrapper:
def as_sql(self, compiler: SQLCompiler = ..., connection: Any = ...) -> Any: ...
class Q(tree.Node):
children: Union[List[Dict[str, str]], List[Tuple[str, Any]], List[Q]]
connector: str
negated: bool
AND: str = ...
OR: str = ...
default: Any = ...
conditional: bool = ...
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
def __or__(self, other: Any) -> Q: ...
@@ -46,8 +42,8 @@ class Q(tree.Node):
class DeferredAttribute:
field_name: str = ...
field: Field
def __init__(self, field_name: str) -> None: ...
def __get__(self, instance: Optional[Model], cls: Type[Model] = ...) -> Any: ...
class RegisterLookupMixin:
lookup_name: str
@@ -65,15 +61,11 @@ class RegisterLookupMixin:
def select_related_descend(
field: Field,
restricted: bool,
requested: Optional[
Union[Dict[str, Dict[str, Dict[str, Dict[str, Dict[str, Dict[str, Dict[str, Dict[Any, Any]]]]]]]], bool]
],
load_fields: Optional[Set[str]],
requested: Optional[Mapping[str, Any]],
load_fields: Optional[Collection[str]],
reverse: bool = ...,
) -> bool: ...
def refs_expression(
lookup_parts: List[str], annotations: OrderedDict
) -> Union[Tuple[bool, Tuple], Tuple[Expression, List[str]]]: ...
def refs_expression(lookup_parts: Sequence[str], annotations: Mapping[str, bool]) -> Tuple[bool, Sequence[str]]: ...
def check_rel_lookup_compatibility(model: Type[Model], target_opts: Any, field: FieldCacheMixin) -> bool: ...
class FilteredRelation:

View File

@@ -25,7 +25,7 @@ class SQLCompiler:
def setup_query(self) -> None: ...
has_extra_select: Any = ...
def pre_sql_setup(
self
self,
) -> Tuple[
List[Tuple[Expression, Tuple[str, Union[List[Any], Tuple[str, str]]], None]],
List[Tuple[Expression, Tuple[str, List[Union[int, str]], bool]]],
@@ -40,7 +40,7 @@ class SQLCompiler:
self, expressions: List[Expression], having: Union[List[Expression], Tuple]
) -> List[Expression]: ...
def get_select(
self
self,
) -> Tuple[
List[Tuple[Expression, Tuple[str, List[Union[int, str]]], Optional[str]]],
Optional[Dict[str, Any]],

View File

@@ -1,6 +1,6 @@
import collections
from collections import OrderedDict, namedtuple
from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union
from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union, Iterable
from django.db.models.lookups import Lookup, Transform
from django.db.models.query_utils import PathInfo, RegisterLookupMixin
@@ -155,19 +155,19 @@ class Query:
def add_ordering(self, *ordering: Any) -> None: ...
def clear_ordering(self, force_empty: bool) -> None: ...
def set_group_by(self) -> None: ...
def add_select_related(self, fields: Tuple[str]) -> None: ...
def add_select_related(self, fields: Iterable[str]) -> None: ...
def add_extra(
self,
select: Optional[Union[Dict[str, int], Dict[str, str], OrderedDict]],
select_params: Optional[Union[List[int], List[str], Tuple[int]]],
where: Optional[List[str]],
params: Optional[List[str]],
tables: Optional[List[str]],
order_by: Optional[Union[List[str], Tuple[str]]],
select: Optional[Dict[str, Any]],
select_params: Optional[Iterable[Any]],
where: Optional[Sequence[str]],
params: Optional[Sequence[str]],
tables: Optional[Sequence[str]],
order_by: Optional[Sequence[str]],
) -> None: ...
def clear_deferred_loading(self) -> None: ...
def add_deferred_loading(self, field_names: Tuple[str]) -> None: ...
def add_immediate_loading(self, field_names: Tuple[str]) -> None: ...
def add_deferred_loading(self, field_names: Iterable[str]) -> None: ...
def add_immediate_loading(self, field_names: Iterable[str]) -> None: ...
def get_loaded_field_names(self) -> Dict[Type[Model], Set[str]]: ...
def get_loaded_field_names_cb(
self, target: Dict[Type[Model], Set[str]], model: Type[Model], fields: Set[Field]

View File

@@ -2,6 +2,8 @@ from django.core.exceptions import ValidationError as ValidationError
from .forms import Form as Form, BaseForm as BaseForm
from .formsets import BaseFormSet as BaseFormSet, all_valid as all_valid, formset_factory as formset_factory
from .models import (
ModelForm as ModelForm,
ModelChoiceField as ModelChoiceField,

View File

@@ -68,4 +68,6 @@ class BaseForm:
def visible_fields(self): ...
def get_initial_for_field(self, field: Field, field_name: str) -> Any: ...
class Form(BaseForm): ...
class Form(BaseForm):
base_fields: Dict[str, Field]
declared_fields: Dict[str, Field]

View File

@@ -63,7 +63,7 @@ class HttpRequest(BytesIO):
self, key: str, default: Any = ..., salt: str = ..., max_age: Optional[int] = ...
) -> Optional[str]: ...
def get_raw_uri(self) -> str: ...
def build_absolute_uri(self, location: str = ...) -> str: ...
def build_absolute_uri(self, location: Optional[str] = ...) -> str: ...
@property
def scheme(self) -> Optional[str]: ...
def is_secure(self) -> bool: ...

View File

@@ -63,7 +63,7 @@ class HttpResponseBase(Iterable[Any]):
class HttpResponse(HttpResponseBase):
client: Client
context: Optional[Context]
context: Context
csrf_cookie_set: bool
redirect_chain: List[Tuple[str, int]]
request: Dict[str, Any]

View File

@@ -1,7 +1,11 @@
from typing import Any, Callable, Dict, List, Optional, Protocol, Sequence, Type, TypeVar, Union
from django.db.models.base import Model
from django.http.response import HttpResponse as HttpResponse, HttpResponseRedirect as HttpResponseRedirect
from django.http.response import (
HttpResponse as HttpResponse,
HttpResponseRedirect as HttpResponseRedirect,
HttpResponsePermanentRedirect as HttpResponsePermanentRedirect,
)
from django.db.models import Manager, QuerySet
from django.http import HttpRequest
@@ -26,7 +30,7 @@ class SupportsGetAbsoluteUrl(Protocol): ...
def redirect(
to: Union[Callable, str, SupportsGetAbsoluteUrl], *args: Any, permanent: bool = ..., **kwargs: Any
) -> HttpResponseRedirect: ...
) -> Union[HttpResponseRedirect, HttpResponsePermanentRedirect]: ...
_T = TypeVar("_T", bound=Model)

View File

@@ -1,7 +1,7 @@
from io import BytesIO
from typing import Any, Dict, List, Optional, Pattern, Tuple, Type, Union
from django.contrib.auth.models import User
from django.contrib.auth.models import AbstractUser
from django.contrib.sessions.backends.base import SessionBase
from django.core.handlers.base import BaseHandler
from django.core.serializers.json import DjangoJSONEncoder
@@ -9,6 +9,8 @@ from django.http.cookie import SimpleCookie
from django.http.request import HttpRequest
from django.http.response import HttpResponse, HttpResponseBase
from django.core.handlers.wsgi import WSGIRequest
BOUNDARY: str = ...
MULTIPART_CONTENT: str = ...
CONTENT_TYPE_RE: Pattern = ...
@@ -40,13 +42,13 @@ class RequestFactory:
cookies: SimpleCookie = ...
errors: BytesIO = ...
def __init__(self, *, json_encoder: Any = ..., **defaults: Any) -> None: ...
def request(self, **request: Any) -> HttpRequest: ...
def get(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> HttpRequest: ...
def request(self, **request: Any) -> WSGIRequest: ...
def get(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> WSGIRequest: ...
def post(
self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any
) -> HttpRequest: ...
def head(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> HttpRequest: ...
def trace(self, path: str, secure: bool = ..., **extra: Any) -> HttpRequest: ...
) -> WSGIRequest: ...
def head(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> WSGIRequest: ...
def trace(self, path: str, secure: bool = ..., **extra: Any) -> WSGIRequest: ...
def options(
self,
path: str,
@@ -54,16 +56,16 @@ class RequestFactory:
content_type: str = ...,
secure: bool = ...,
**extra: Any
) -> HttpRequest: ...
) -> WSGIRequest: ...
def put(
self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any
) -> HttpRequest: ...
) -> WSGIRequest: ...
def patch(
self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any
) -> HttpRequest: ...
) -> WSGIRequest: ...
def delete(
self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any
) -> HttpRequest: ...
) -> WSGIRequest: ...
def generic(
self,
method: str,
@@ -72,7 +74,7 @@ class RequestFactory:
content_type: Optional[str] = ...,
secure: bool = ...,
**extra: Any
) -> HttpRequest: ...
) -> WSGIRequest: ...
class Client:
json_encoder: Type[DjangoJSONEncoder] = ...
@@ -82,13 +84,11 @@ class Client:
handler: ClientHandler = ...
exc_info: None = ...
def __init__(self, enforce_csrf_checks: bool = ..., **defaults: Any) -> None: ...
def request(self, **request: Any) -> HttpResponse: ...
def get(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> HttpResponse: ...
def post(
self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any
) -> HttpResponse: ...
def head(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> HttpResponse: ...
def trace(self, path: str, secure: bool = ..., **extra: Any) -> HttpResponse: ...
def request(self, **request: Any) -> Any: ...
def get(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> Any: ...
def post(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ...
def head(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> Any: ...
def trace(self, path: str, secure: bool = ..., **extra: Any) -> Any: ...
def options(
self,
path: str,
@@ -96,16 +96,10 @@ class Client:
content_type: str = ...,
secure: bool = ...,
**extra: Any
) -> HttpResponse: ...
def put(
self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any
) -> HttpResponse: ...
def patch(
self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any
) -> HttpResponse: ...
def delete(
self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any
) -> HttpResponse: ...
) -> Any: ...
def put(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ...
def patch(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ...
def delete(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ...
def generic(
self,
method: str,
@@ -114,12 +108,12 @@ class Client:
content_type: Optional[str] = ...,
secure: bool = ...,
**extra: Any
) -> HttpResponse: ...
) -> Any: ...
def store_exc_info(self, **kwargs: Any) -> None: ...
@property
def session(self) -> SessionBase: ...
def login(self, **credentials: Any) -> bool: ...
def force_login(self, user: User, backend: Optional[str] = ...) -> None: ...
def force_login(self, user: AbstractUser, backend: Optional[str] = ...) -> None: ...
def logout(self) -> None: ...
def conditional_content_removal(request: HttpRequest, response: HttpResponseBase) -> HttpResponse: ...

View File

@@ -1,7 +1,7 @@
import threading
import unittest
from datetime import date
from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple, Type, Union
from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple, Type, Union, ClassVar
from django.core.exceptions import ImproperlyConfigured
from django.core.handlers.wsgi import WSGIHandler
@@ -47,7 +47,8 @@ class SimpleTestCase(unittest.TestCase):
client_class: Any = ...
client: Client
allow_database_queries: bool = ...
databases: Set[str] = ...
# TODO: str -> Literal['__all__']
databases: Union[Set[str], str] = ...
def __call__(self, result: Optional[unittest.TestResult] = ...) -> None: ...
def settings(self, **kwargs: Any) -> Any: ...
def modify_settings(self, **kwargs: Any) -> Any: ...
@@ -198,13 +199,12 @@ class LiveServerThread(threading.Thread):
def terminate(self) -> None: ...
class LiveServerTestCase(TransactionTestCase):
live_server_url: ClassVar[str]
host: str = ...
port: int = ...
server_thread_class: Type[Any] = ...
server_thread: Any
static_handler: Any = ...
@classmethod
def live_server_url(cls): ...
class SerializeMixin:
lockfile: Any = ...

View File

@@ -1,9 +1,22 @@
import decimal
import warnings
from contextlib import contextmanager
from decimal import Decimal
from io import StringIO
from typing import Any, Callable, Dict, Iterable, Iterator, List, Mapping, Optional, Set, Tuple, Type, Union
from typing import (
Any,
Callable,
Dict,
Iterable,
Iterator,
List,
Mapping,
Optional,
Set,
Tuple,
Type,
Union,
ContextManager,
)
from django.apps.registry import Apps
from django.core.checks.registry import CheckRegistry
@@ -86,7 +99,7 @@ class ignore_warnings(TestContextDecorator):
ignore_kwargs: Dict[str, Any] = ...
filter_func: Callable = ...
def __init__(self, **kwargs: Any) -> None: ...
catch_warnings: warnings.catch_warnings = ...
catch_warnings: ContextManager[Optional[list]] = ...
requires_tz_support: Any

View File

@@ -1,4 +1,5 @@
from os.path import abspath
from pathlib import Path
from typing import Any, Union
abspathu = abspath
@@ -7,3 +8,4 @@ def upath(path: Any): ...
def npath(path: Any): ...
def safe_join(base: Union[bytes, str], *paths: Any) -> str: ...
def symlinks_supported() -> Any: ...
def to_path(value: Union[Path, str]) -> Path: ...

View File

@@ -5,6 +5,7 @@ from django.http.response import HttpResponse
class RemovedInDjango30Warning(PendingDeprecationWarning): ...
class RemovedInDjango31Warning(PendingDeprecationWarning): ...
class RemovedInDjango40Warning(PendingDeprecationWarning): ...
class RemovedInNextVersionWarning(DeprecationWarning): ...
class warn_about_renamed_method:

View File

@@ -25,6 +25,9 @@ def urlsafe_base64_decode(s: Union[bytes, str]) -> bytes: ...
def parse_etags(etag_str: str) -> List[str]: ...
def quote_etag(etag_str: str) -> str: ...
def is_same_domain(host: str, pattern: str) -> bool: ...
def url_has_allowed_host_and_scheme(
url: Optional[str], allowed_hosts: Optional[Union[str, Iterable[str]]], require_https: bool = ...
) -> bool: ...
def is_safe_url(
url: Optional[str], allowed_hosts: Optional[Union[str, Iterable[str]]], require_https: bool = ...
) -> bool: ...

View File

@@ -1,8 +1,10 @@
import types
from contextlib import ContextDecorator
from datetime import date, datetime as datetime, time, timedelta as timedelta, tzinfo as tzinfo
from datetime import date, datetime as datetime, time, timedelta as timedelta, tzinfo as tzinfo, timezone
from typing import Optional, Union, Type
from pytz import BaseTzInfo
_AnyTime = Union[time, datetime]
class UTC(tzinfo):
@@ -30,10 +32,14 @@ class LocalTimezone(ReferenceLocalTimezone):
utc: UTC = ...
def get_fixed_timezone(offset: Union[timedelta, int]) -> tzinfo: ...
def get_default_timezone() -> tzinfo: ...
def get_fixed_timezone(offset: Union[timedelta, int]) -> timezone: ...
def get_default_timezone() -> BaseTzInfo: ...
def get_default_timezone_name() -> str: ...
def get_current_timezone() -> tzinfo: ...
# Strictly speaking, it is possible to activate() a non-pytz timezone,
# in which case BaseTzInfo is incorrect. However, this is unlikely,
# so we use it anyway, to keep things ergonomic for most users.
def get_current_timezone() -> BaseTzInfo: ...
def get_current_timezone_name() -> str: ...
def activate(timezone: Union[tzinfo, str]) -> None: ...
def deactivate() -> None: ...

View File

@@ -1,16 +1,16 @@
from typing import Any, Dict, Iterable, Optional, Tuple, Union, Sequence
from typing import Any, Dict, Iterable, Optional, Tuple, Union, Sequence, List
from django.db.models.sql.where import NothingNode
_NodeChildren = Iterable[Union["Node", NothingNode, Sequence[Any]]]
class Node:
default: str = ...
children: List[Any]
default: Any = ...
connector: str = ...
negated: bool = ...
def __init__(
self,
children: Optional[Iterable[Union[Node, NothingNode, Sequence[Any]]]] = ...,
connector: Optional[str] = ...,
negated: bool = ...,
self, children: Optional[_NodeChildren] = ..., connector: Optional[str] = ..., negated: bool = ...,
) -> None: ...
def __deepcopy__(self, memodict: Dict[Any, Any]) -> Node: ...
def __len__(self) -> int: ...

View File

@@ -63,4 +63,4 @@ class ExceptionReporter:
): ...
def technical_404_response(request: HttpRequest, exception: Http404) -> HttpResponse: ...
def default_urlconf(request: HttpRequest) -> HttpResponse: ...
def default_urlconf(request: Optional[HttpResponse]) -> HttpResponse: ...

View File

@@ -13,6 +13,7 @@ class View:
def __init__(self, **kwargs: Any) -> None: ...
@classmethod
def as_view(cls: Any, **initkwargs: Any) -> Callable[..., http.HttpResponse]: ...
def setup(self, request: http.HttpRequest, *args: Any, **kwargs: Any) -> None: ...
def dispatch(self, request: http.HttpRequest, *args: Any, **kwargs: Any) -> http.HttpResponse: ...
def http_method_not_allowed(self, request: http.HttpRequest, *args: Any, **kwargs: Any) -> http.HttpResponse: ...
def options(self, request: http.HttpRequest, *args: Any, **kwargs: Any) -> http.HttpResponse: ...

View File

@@ -367,7 +367,7 @@ class DjangoContext:
lookup_type: MypyType = lookup_base.args[0]
# if it's Field, consider lookup_type a __get__ of current field
if (isinstance(lookup_type, Instance)
and lookup_type.type.fullname() == fullnames.FIELD_FULLNAME):
and lookup_type.type.fullname == fullnames.FIELD_FULLNAME):
field_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), field.__class__)
if field_info is None:
return AnyType(TypeOfAny.explicit)

View File

@@ -179,26 +179,20 @@ def add_new_class_for_module(module: MypyFile, name: str, bases: List[Instance],
# make new class expression
classdef = ClassDef(new_class_unique_name, Block([]))
classdef.fullname = module.fullname() + '.' + new_class_unique_name
classdef.fullname = module.fullname + '.' + new_class_unique_name
# make new TypeInfo
new_typeinfo = TypeInfo(SymbolTable(), classdef, module.fullname())
new_typeinfo = TypeInfo(SymbolTable(), classdef, module.fullname)
new_typeinfo.bases = bases
calculate_mro(new_typeinfo)
new_typeinfo.calculate_metaclass_type()
def add_field_to_new_typeinfo(var: Var, is_initialized_in_class: bool = False,
is_property: bool = False) -> None:
var.info = new_typeinfo
var.is_initialized_in_class = is_initialized_in_class
var.is_property = is_property
var._fullname = new_typeinfo.fullname() + '.' + var.name()
new_typeinfo.names[var.name()] = SymbolTableNode(MDEF, var)
# add fields
var_items = [Var(item, typ) for item, typ in fields.items()]
for var_item in var_items:
add_field_to_new_typeinfo(var_item, is_property=True)
for field_name, field_type in fields.items():
var = Var(field_name, type=field_type)
var.info = new_typeinfo
var._fullname = new_typeinfo.fullname + '.' + field_name
new_typeinfo.names[field_name] = SymbolTableNode(MDEF, var, plugin_generated=True)
classdef.info = new_typeinfo
module.names[new_class_unique_name] = SymbolTableNode(GDEF, new_typeinfo, plugin_generated=True)
@@ -282,7 +276,7 @@ def get_typechecker_api(ctx: Union[AttributeContext, MethodContext, FunctionCont
def is_model_subclass_info(info: TypeInfo, django_context: 'DjangoContext') -> bool:
return (info.fullname() in django_context.all_registered_model_class_fullnames
return (info.fullname in django_context.all_registered_model_class_fullnames
or info.has_base(fullnames.MODEL_CLASS_FULLNAME))
@@ -299,7 +293,7 @@ def add_new_sym_for_info(info: TypeInfo, *, name: str, sym_type: MypyType) -> No
var = Var(name=name, type=sym_type)
# var.info: type of the object variable is bound to
var.info = info
var._fullname = info.fullname() + '.' + name
var._fullname = info.fullname + '.' + name
var.is_initialized_in_class = True
var.is_inferred = True
info.names[name] = SymbolTableNode(MDEF, var,

View File

@@ -121,17 +121,17 @@ class NewSemanalDjangoPlugin(Plugin):
def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
# for settings
if file.fullname() == 'django.conf' and self.django_context.django_settings_module:
if file.fullname == 'django.conf' and self.django_context.django_settings_module:
return [self._new_dependency(self.django_context.django_settings_module)]
# for values / values_list
if file.fullname() == 'django.db.models':
if file.fullname == 'django.db.models':
return [self._new_dependency('mypy_extensions'), self._new_dependency('typing')]
# for `get_user_model()`
if self.django_context.settings:
if (file.fullname() == 'django.contrib.auth'
or file.fullname() in {'django.http', 'django.http.request'}):
if (file.fullname == 'django.contrib.auth'
or file.fullname in {'django.http', 'django.http.request'}):
auth_user_model_name = self.django_context.settings.AUTH_USER_MODEL
try:
auth_user_module = self.django_context.apps_registry.get_model(auth_user_model_name).__module__
@@ -141,7 +141,7 @@ class NewSemanalDjangoPlugin(Plugin):
return [self._new_dependency(auth_user_module)]
# ensure that all mentioned to='someapp.SomeModel' are loaded with corresponding related Fields
defined_model_classes = self.django_context.model_modules.get(file.fullname())
defined_model_classes = self.django_context.model_modules.get(file.fullname)
if not defined_model_classes:
return []
deps = set()
@@ -153,13 +153,13 @@ class NewSemanalDjangoPlugin(Plugin):
if related_model_cls is None:
continue
related_model_module = related_model_cls.__module__
if related_model_module != file.fullname():
if related_model_module != file.fullname:
deps.add(self._new_dependency(related_model_module))
# reverse relations
for relation in model_class._meta.related_objects:
related_model_cls = self.django_context.get_field_related_model_cls(relation)
related_model_module = related_model_cls.__module__
if related_model_module != file.fullname():
if related_model_module != file.fullname:
deps.add(self._new_dependency(related_model_module))
return list(deps)

View File

@@ -29,7 +29,7 @@ def _get_current_field_from_assignment(ctx: FunctionContext, django_context: Dja
if field_name is None:
return None
model_cls = django_context.get_model_class_by_fullname(outer_model_info.fullname())
model_cls = django_context.get_model_class_by_fullname(outer_model_info.fullname)
if model_cls is None:
return None

View File

@@ -54,7 +54,7 @@ def typecheck_model_method(ctx: Union[FunctionContext, MethodContext], django_co
def redefine_and_typecheck_model_init(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
assert isinstance(ctx.default_return_type, Instance)
model_fullname = ctx.default_return_type.type.fullname()
model_fullname = ctx.default_return_type.type.fullname
model_cls = django_context.get_model_class_by_fullname(model_fullname)
if model_cls is None:
return ctx.default_return_type
@@ -67,7 +67,7 @@ def redefine_and_typecheck_model_create(ctx: MethodContext, django_context: Djan
# only work with ctx.default_return_type = model Instance
return ctx.default_return_type
model_fullname = ctx.default_return_type.type.fullname()
model_fullname = ctx.default_return_type.type.fullname
model_cls = django_context.get_model_class_by_fullname(model_fullname)
if model_cls is None:
return ctx.default_return_type

View File

@@ -28,7 +28,7 @@ def return_proper_field_type_from_get_field(ctx: MethodContext, django_context:
if not isinstance(model_type, Instance):
return ctx.default_return_type
model_cls = django_context.get_model_class_by_fullname(model_type.type.fullname())
model_cls = django_context.get_model_class_by_fullname(model_type.type.fullname)
if model_cls is None:
return ctx.default_return_type

View File

@@ -1,5 +1,5 @@
from collections import OrderedDict
from typing import Type
from typing import List, Tuple, Type
from django.db.models.base import Model
from django.db.models.fields import DateField, DateTimeField
@@ -7,10 +7,11 @@ from django.db.models.fields.related import ForeignKey
from django.db.models.fields.reverse_related import (
ManyToManyRel, ManyToOneRel, OneToOneRel,
)
from mypy.nodes import ARG_STAR2, Argument, Context, TypeInfo, Var
from mypy.nodes import ARG_STAR2, Argument, Context, FuncDef, TypeInfo, Var
from mypy.plugin import ClassDefContext
from mypy.plugins import common
from mypy.types import AnyType, Instance
from mypy.plugins.common import add_method
from mypy.types import AnyType, CallableType, Instance
from mypy.types import Type as MypyType
from mypy.types import TypeOfAny
@@ -43,7 +44,7 @@ class ModelClassInitializer:
var = Var(name=name, type=typ)
# var.info: type of the object variable is bound to
var.info = self.model_classdef.info
var._fullname = self.model_classdef.info.fullname() + '.' + name
var._fullname = self.model_classdef.info.fullname + '.' + name
var.is_initialized_in_class = True
var.is_inferred = True
return var
@@ -126,7 +127,7 @@ class AddRelatedModelsId(ModelClassInitializer):
class AddManagers(ModelClassInitializer):
def _is_manager_any(self, typ: Instance) -> bool:
return typ.type.fullname() == fullnames.MANAGER_CLASS_FULLNAME and type(typ.args[0]) == AnyType
return typ.type.fullname == fullnames.MANAGER_CLASS_FULLNAME and type(typ.args[0]) == AnyType
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
for manager_name, manager in model_cls._meta.managers_map.items():
@@ -141,6 +142,7 @@ class AddManagers(ModelClassInitializer):
has_manager_any_base = any(self._is_manager_any(base) for base in manager_info.bases)
if has_manager_any_base:
custom_model_manager_name = manager.model.__name__ + '_' + manager.__class__.__name__
bases = []
for original_base in manager_info.bases:
if self._is_manager_any(original_base):
@@ -150,14 +152,53 @@ class AddManagers(ModelClassInitializer):
original_base = helpers.reparametrize_instance(original_base,
[Instance(self.model_classdef.info, [])])
bases.append(original_base)
current_module = self.api.modules[self.model_classdef.info.module_name]
custom_manager_info = helpers.add_new_class_for_module(current_module,
custom_model_manager_name,
bases=bases,
fields=OrderedDict())
# copy fields to a new manager
new_cls_def_context = ClassDefContext(cls=custom_manager_info.defn,
reason=self.ctx.reason,
api=self.api)
custom_manager_type = Instance(custom_manager_info, [Instance(self.model_classdef.info, [])])
for name, sym in manager_info.names.items():
# replace self type with new class, if copying method
if isinstance(sym.node, FuncDef):
arguments, return_type = self.prepare_new_method_arguments(sym.node)
add_method(new_cls_def_context,
name,
args=arguments,
return_type=return_type,
self_type=custom_manager_type)
continue
new_sym = sym.copy()
if isinstance(new_sym.node, Var):
new_var = Var(name, type=sym.type)
new_var.info = custom_manager_info
new_var._fullname = custom_manager_info.fullname + '.' + name
new_sym.node = new_var
custom_manager_info.names[name] = new_sym
self.add_new_node_to_model_class(manager_name, custom_manager_type)
def prepare_new_method_arguments(self, node: FuncDef) -> Tuple[List[Argument], MypyType]:
arguments = []
for argument in node.arguments[1:]:
if argument.type_annotation is None:
argument.type_annotation = AnyType(TypeOfAny.unannotated)
arguments.append(argument)
if isinstance(node.type, CallableType):
return_type = node.type.ret_type
else:
return_type = AnyType(TypeOfAny.unannotated)
return arguments, return_type
class AddDefaultManagerAttribute(ModelClassInitializer):
def run_with_model_cls(self, model_cls: Type[Model]) -> None:

View File

@@ -16,7 +16,7 @@ def typecheck_queryset_filter(ctx: MethodContext, django_context: DjangoContext)
if not ctx.type.args or not isinstance(ctx.type.args[0], Instance):
return ctx.default_return_type
model_cls_fullname = ctx.type.args[0].type.fullname()
model_cls_fullname = ctx.type.args[0].type.fullname
model_cls = django_context.get_model_class_by_fullname(model_cls_fullname)
if model_cls is None:
return ctx.default_return_type
@@ -44,7 +44,7 @@ def typecheck_queryset_filter(ctx: MethodContext, django_context: DjangoContext)
def resolve_combinable_type(combinable_type: Instance, django_context: DjangoContext) -> MypyType:
if combinable_type.type.fullname() != fullnames.F_EXPRESSION_FULLNAME:
if combinable_type.type.fullname != fullnames.F_EXPRESSION_FULLNAME:
# Combinables aside from F expressions are unsupported
return AnyType(TypeOfAny.explicit)

View File

@@ -117,7 +117,7 @@ def extract_proper_type_queryset_values_list(ctx: MethodContext, django_context:
if model_type is None:
return AnyType(TypeOfAny.from_omitted_generics)
model_cls = django_context.get_model_class_by_fullname(model_type.type.fullname())
model_cls = django_context.get_model_class_by_fullname(model_type.type.fullname)
if model_cls is None:
return ctx.default_return_type
@@ -166,7 +166,7 @@ def extract_proper_type_queryset_values(ctx: MethodContext, django_context: Djan
if model_type is None:
return AnyType(TypeOfAny.from_omitted_generics)
model_cls = django_context.get_model_class_by_fullname(model_type.type.fullname())
model_cls = django_context.get_model_class_by_fullname(model_type.type.fullname)
if model_cls is None:
return ctx.default_return_type

View File

@@ -0,0 +1,88 @@
import os
from typing import Optional
import libcst
from libcst import Annotation, BaseExpression, FunctionDef, Name, Subscript
from libcst.metadata import SyntacticPositionProvider
BASE_DIR = 'django-stubs'
fpath = os.path.join(BASE_DIR, 'core', 'checks', 'model_checks.pyi')
with open(fpath, 'r') as f:
contents = f.read()
tree = libcst.parse_module(contents)
class TypeAnnotationsAnalyzer(libcst.CSTVisitor):
METADATA_DEPENDENCIES = (SyntacticPositionProvider,)
def __init__(self, fpath: str):
super().__init__()
self.fpath = fpath
def get_node_location(self, node: FunctionDef) -> str:
start_line = self.get_metadata(SyntacticPositionProvider, node).start.line
return f'{self.fpath}:{start_line}'
def show_error_for_node(self, node: FunctionDef, error_message: str):
print(self.get_node_location(node), error_message)
def check_subscripted_annotation(self, annotation: BaseExpression) -> Optional[str]:
if isinstance(annotation, Subscript):
if isinstance(annotation.value, Name):
error_message = self.check_concrete_class_usage(annotation.value)
if error_message:
return error_message
if annotation.value.value == 'Union':
for slice_param in annotation.slice:
if isinstance(slice_param.slice.value, Name):
error_message = self.check_concrete_class_usage(annotation.value)
if error_message:
return error_message
def check_concrete_class_usage(self, name_node: Name) -> Optional[str]:
if name_node.value == 'List':
return (f'Concrete class {name_node.value!r} used for an iterable annotation. '
f'Use abstract collection (Iterable, Collection, Sequence) instead')
def visit_FunctionDef(self, node: FunctionDef) -> Optional[bool]:
params_node = node.params
for param_node in [*params_node.params, *params_node.default_params]:
param_name = param_node.name.value
annotation_node = param_node.annotation # type: Annotation
if annotation_node is not None:
annotation = annotation_node.annotation
if annotation.value == 'None':
self.show_error_for_node(node, f'"None" type annotation used for parameter {param_name!r}')
continue
error_message = self.check_subscripted_annotation(annotation)
if error_message is not None:
self.show_error_for_node(node, error_message)
continue
if node.returns is not None:
return_annotation = node.returns.annotation
if isinstance(return_annotation, Subscript) and return_annotation.value.value == 'Union':
self.show_error_for_node(node, 'Union is return type annotation')
return False
for dirpath, dirnames, filenames in os.walk(BASE_DIR):
for filename in filenames:
fpath = os.path.join(dirpath, filename)
# skip all other checks for now, low priority
if not fpath.startswith(('django-stubs/db', 'django-stubs/views', 'django-stubs/apps',
'django-stubs/http', 'django-stubs/contrib/postgres')):
continue
with open(fpath, 'r') as f:
contents = f.read()
tree = libcst.MetadataWrapper(libcst.parse_module(contents))
analyzer = TypeAnnotationsAnalyzer(fpath)
tree.visit(analyzer)

View File

@@ -1,3 +1,5 @@
import django
SECRET_KEY = '1'
SITE_ID = 1
@@ -41,7 +43,6 @@ test_modules = [
'bulk_create',
'cache',
'check_framework',
'choices',
'conditional_processing',
'constraints',
'contenttypes_tests',
@@ -219,6 +220,9 @@ test_modules = [
'wsgi',
]
if django.VERSION[0] == 2:
test_modules += ['choices']
invalid_apps = {
'import_error_package',
}

View File

@@ -5,13 +5,13 @@ import re
IGNORED_MODULES = {'schema', 'gis_tests', 'admin_widgets', 'admin_filters', 'migrations',
'sitemaps_tests', 'staticfiles_tests', 'modeladmin', 'model_forms',
'generic_views', 'forms_tests', 'flatpages_tests', 'admin_utils',
'admin_ordering', 'admin_changelist', 'admin_views', 'mail', 'redirects_tests',
'admin_ordering', 'admin_changelist', 'admin_views', 'redirects_tests',
'invalid_models_tests', 'i18n', 'migrate_signals', 'model_formsets',
'template_tests', 'template_backends', 'test_runner', 'admin_scripts',
'sites_tests', 'inline_formsets', 'foreign_object', 'cache', 'test_client', 'test_client_regress'}
MOCK_OBJECTS = ['MockRequest', 'MockCompiler', 'modelz', 'call_count', 'call_args_list',
'call_args', 'MockUser', 'Xtemplate', 'DummyRequest', 'DummyUser', 'MinimalUser']
'call_args', 'MockUser', 'Xtemplate', 'DummyRequest', 'DummyUser', 'MinimalUser', 'DummyNode']
EXTERNAL_MODULES = ['psycopg2', 'PIL', 'selenium', 'oracle', 'mysql', 'sqlite3', 'sqlparse', 'tblib', 'numpy',
'bcrypt', 'argon2', 'xml.dom']
IGNORED_ERRORS = {
@@ -25,8 +25,7 @@ IGNORED_ERRORS = {
'Cannot assign to a type',
'"HttpResponseBase" has no attribute',
'"object" has no attribute',
re.compile(r'"Callable\[(\[(Any(, )?)*((, )?VarArg\(Any\))?((, )?KwArg\(Any\))?\]|\.\.\.), Any\]" '
r'has no attribute'),
re.compile(r'"Callable\[.+, Any\]" has no attribute'),
'has no attribute "deconstruct"',
# private members
re.compile(r'has no attribute ("|\')_[a-zA-Z_]+("|\')'),
@@ -35,10 +34,9 @@ IGNORED_ERRORS = {
re.compile(r"Expression of type '.*' is not supported"),
'has incompatible type "object"',
'undefined in superclass',
'Argument after ** must be a mapping, not "object"',
'Argument after ** must be a mapping',
'note:',
re.compile(r'Item "None" of "[a-zA-Z_ ,\[\]]+" has no attribute'),
'"Optional[List[_Record]]"',
'"Callable[..., None]" has no attribute',
'does not return a value',
'has no attribute "alternatives"',
@@ -61,7 +59,12 @@ IGNORED_ERRORS = {
# TODO: multitable inheritance
'ptr',
'Incompatible types in assignment (expression has type "Callable[',
'SimpleLazyObject'
'SimpleLazyObject',
'Test',
'Mixin" has no attribute',
'Incompatible types in string interpolation',
'"None" has no attribute',
'has no attribute "assert',
],
'aggregation': [
re.compile(r'got "Optional\[(Author|Publisher)\]", expected "Union\[(Author|Publisher), Combinable\]"'),
@@ -83,52 +86,69 @@ IGNORED_ERRORS = {
'Unsupported left operand type for + ("Sequence[str]")',
'AuthenticationFormWithInactiveUsersOkay',
'Incompatible types in assignment (expression has type "Dict[str, Any]", variable has type "QueryDict")',
'No overload variant of "int" matches argument type "AnonymousUser"',
'expression has type "AnonymousUser", variable has type "User"',
],
'basic': [
'Unexpected keyword argument "unknown_kwarg" for "refresh_from_db" of "Model"',
'Unexpected attribute "foo" for model "Article"',
'has no attribute "touched"',
'Incompatible types in assignment (expression has type "Type[CustomQuerySet]"'
'Incompatible types in assignment (expression has type "Type[CustomQuerySet]"',
'"Manager[Article]" has no attribute "do_something"',
],
'backends': [
'"DatabaseError" has no attribute "pgcode"'
],
'builtin_server': [
'"ServerHandler" has no attribute',
],
'bulk_create': [
'has incompatible type "List[Country]"; expected "Iterable[TwoFields]"',
'List item 1 has incompatible type "Country"; expected "ProxyCountry"',
],
'check_framework': [
'base class "Model" defined the type as "Callable',
'Value of type "Collection[str]" is not indexable',
'Unsupported target for indexed assignment',
],
'constraints': [
'Argument "condition" to "UniqueConstraint" has incompatible type "str"; expected "Optional[Q]"'
],
'contenttypes_tests': [
# 'Item "Model" of "Union[GenericForeignKey, Model, None]" has no attribute'
'base class "BaseOrderWithRespectToTests" defined the type as "None"'
'"FooWithBrokenAbsoluteUrl" has no attribute "unknown_field"',
'contenttypes_tests.models.Site',
'Argument 1 to "set" of "RelatedManager" has incompatible type "SiteManager[Site]"',
],
'custom_lookups': [
'in base class "SQLFuncMixin"'
'in base class "SQLFuncMixin"',
'has no attribute "name"',
],
'custom_columns': [
"Cannot resolve keyword 'firstname' into field",
],
'custom_pk': [
'"Employee" has no attribute "id"'
'"Employee" has no attribute "id"',
],
'custom_managers': [
'Unsupported dynamic base class',
'Incompatible types in assignment (expression has type "CharField',
'Item "Book" of "Optional[Book]" has no attribute "favorite_avg"'
'Item "Book" of "Optional[Book]" has no attribute "favorite_avg"',
],
'csrf_tests': [
'Incompatible types in assignment (expression has type "property", ' +
'base class "HttpRequest" defined the type as "QueryDict")'
'expression has type "property", base class "HttpRequest" defined the type as "QueryDict"',
'expression has type "Dict[<nothing>, <nothing>]", variable has type "SessionBase"',
],
'dates': [
'Too few arguments for "dates" of',
],
'dbshell': [
'Incompatible types in assignment (expression has type "None"',
],
'defer': [
'Too many arguments for "refresh_from_db" of "Model"'
],
'delete': [
'Incompatible type for lookup \'pk\': (got "Optional[int]", expected "int")',
'Incompatible type for lookup \'pk\': (got "Optional[int]", expected "Union[str, int]")',
],
'dispatch': [
'Item "str" of "Union[ValueError, str]" has no attribute "args"'
@@ -144,45 +164,54 @@ IGNORED_ERRORS = {
'decorators': [
'"Type[object]" has no attribute "method"'
],
'expressions_case': [
'Item "Field[Any, Any]" of "Union[Field[Any, Any], ForeignObjectRel]" has no attribute "is_relation"'
],
'expressions_window': [
'has incompatible type "str"'
],
'file_uploads': [
'has no attribute "content_type"',
],
'file_storage': [
'Incompatible types in assignment (expression has type "None", variable has type "str")',
'Property "base_url" defined in "FileSystemStorage" is read-only',
],
'files': [
'Incompatible types in assignment (expression has type "IOBase", variable has type "File")',
'Argument 1 to "write" of "SpooledTemporaryFile"',
],
'filtered_relation': [
'has no attribute "name"',
],
'fixtures': [
'Incompatible types in assignment (expression has type "int", target has type "Iterable[str]")',
'Incompatible types in assignment (expression has type "SpyManager[Spy]"'
'Incompatible types in assignment (expression has type "SpyManager[Spy]"',
],
'fixtures_regress': [
'Unsupported left operand type for + ("None")',
],
'from_db_value': [
'"Cash" has no attribute'
'"Cash" has no attribute',
'Argument 1 to "__str__" of "Decimal"',
],
'get_object_or_404': [
'Argument 1 to "get_object_or_404" has incompatible type "str"; '
+ 'expected "Union[Type[<nothing>], QuerySet[<nothing>]]"',
'Argument 1 to "get_list_or_404" has incompatible type "List[Type[Article]]"; '
+ 'expected "Union[Type[<nothing>], QuerySet[<nothing>]]"',
'CustomClass'
'CustomClass',
],
'generic_relations': [
"Cannot resolve keyword 'vegetable' into field"
"Cannot resolve keyword 'vegetable' into field",
],
'generic_relations_regress': [
'"Link" 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"'
'Argument 1 to "fromkeys" of "QueryDict" has incompatible type "int"',
],
'humanize_tests': [
'Argument 1 to "append" of "list" has incompatible type "None"; expected "str"'
'Argument 1 to "append" of "list" has incompatible type "None"; expected "str"',
],
'lookup': [
'Unexpected keyword argument "headline__startswith" for "in_bulk" of',
@@ -190,18 +219,42 @@ IGNORED_ERRORS = {
"Cannot resolve keyword 'pub_date_year' into field",
"Cannot resolve keyword 'blahblah' into field",
],
'logging_tests': [
re.compile(r'Argument [0-9] to "makeRecord" of "Logger"'),
'"LogRecord" has no attribute "request"',
],
'm2m_regress': [
"Cannot resolve keyword 'porcupine' into field",
'Argument 1 to "set" of "RelatedManager" has incompatible type "int"',
],
'mail': [
'List item 1 has incompatible type "None"; expected "str"',
'Argument 1 to "push" of "SMTPChannel" has incompatible type "str"; expected "bytes"',
'Value of type "Union[List[Message], str, bytes, None]" is not indexable',
'Incompatible types in assignment '
+ '(expression has type "bool", variable has type "Union[SMTP_SSL, SMTP, None]")',
re.compile(
r'Item "(int|str)" of "Union\[Message, str, int, Any\]" has no attribute "(get_content_type|get_filename)"'
)
],
'messages_tests': [
'List item 0 has incompatible type "Dict[str, Message]"; expected "Message"'
'List item 0 has incompatible type "Dict[str, Message]"; expected "Message"',
'Too many arguments',
'CustomRequest',
],
'middleware': [
'"HttpRequest" has no attribute'
re.compile(r'"(HttpRequest|WSGIRequest)" has no attribute'),
],
'many_to_many': [
'(expression has type "List[Article]", variable has type "RelatedManager[Article]"',
'"add" of "RelatedManager" has incompatible type "Article"; expected "Union[Publication, int]"',
],
'many_to_one': [
'Incompatible type for "parent" of "Child" (got "None", expected "Union[Parent, Combinable]")',
'Incompatible type for "parent" of "Child" (got "Child", expected "Union[Parent, Combinable]")'
'Incompatible type for "parent" of "Child" (got "Child", expected "Union[Parent, Combinable]")',
'expression has type "List[<nothing>]", variable has type "RelatedManager[Article]"',
'"Reporter" has no attribute "cached_query"',
'to "add" of "RelatedManager" has incompatible type "Reporter"; expected "Union[Article, int]"',
],
'middleware_exceptions': [
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any]"; expected "str"'
@@ -209,9 +262,10 @@ IGNORED_ERRORS = {
'model_fields': [
'Item "Field[Any, Any]" of "Union[Field[Any, Any], ForeignObjectRel]" has no attribute',
'Incompatible types in assignment (expression has type "Type[Person',
'base class "IntegerFieldTests"',
'ImageFieldTestMixin',
'Incompatible types in assignment (expression has type "FloatModel", variable has type',
'"ImageFile" has no attribute "was_opened"',
'Incompatible type for "size" of "FloatModel" (got "object", expected "Union[float, int, str, Combinable]")',
'Incompatible type for "value" of "IntegerModel" (got "object", expected',
],
'model_indexes': [
'Argument "condition" to "Index" has incompatible type "str"; expected "Optional[Q]"'
@@ -231,6 +285,16 @@ IGNORED_ERRORS = {
'Incompatible type for "department" of "Worker"',
'"PickledModel" has no attribute',
'"Department" has no attribute "evaluate"',
'Unsupported target for indexed assignment',
],
'model_formsets_regress': [
'Incompatible types in assignment (expression has type "int", target has type "str")',
],
'model_options': [
'expression has type "Dict[str, Type[Model]]", target has type "OrderedDict',
],
'model_enums': [
"'bool' is not a valid base class",
],
'multiple_database': [
'Unexpected attribute "extra_arg" for model "Book"'
@@ -239,7 +303,6 @@ IGNORED_ERRORS = {
"Cannot resolve keyword 'foo' into field"
],
'order_with_respect_to': [
'BaseOrderWithRespectToTests',
'"Dimension" has no attribute "set_component_order"',
],
'one_to_one': [
@@ -255,8 +318,6 @@ IGNORED_ERRORS = {
'DummyJSONField',
'Incompatible types in assignment (expression has type "Type[Field[Any, Any]]',
'Argument "encoder" to "JSONField" has incompatible type "DjangoJSONEncoder";',
re.compile(r'Incompatible types in assignment \(expression has type "Type\[.+?\]", '
r'base class "(UnaccentTest|TrigramTest)" defined the type as "Type\[CharFieldModel\]"\)'),
'("None" and "SearchQuery")',
],
'properties': [
@@ -285,12 +346,14 @@ IGNORED_ERRORS = {
'"Collection[Any]" has no attribute "explain"',
"Cannot resolve keyword 'unknown_field' into field",
'Incompatible type for lookup \'tag\': (got "str", expected "Union[Tag, int, None]")',
'No overload variant of "__getitem__" of "QuerySet" matches argument type "str"',
],
'requests': [
'Incompatible types in assignment (expression has type "Dict[str, str]", variable has type "QueryDict")'
],
'responses': [
'Argument 1 to "TextIOWrapper" has incompatible type "HttpResponse"; expected "IO[bytes]"'
'Argument 1 to "TextIOWrapper" has incompatible type "HttpResponse"; expected "IO[bytes]"',
'"FileLike" has no attribute "closed"',
],
'reverse_lookup': [
"Cannot resolve keyword 'choice' into field"
@@ -302,15 +365,16 @@ IGNORED_ERRORS = {
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any, Optional[Any], Any]";'
],
'sites_framework': [
'expression has type "CurrentSiteManager[CustomArticle]", base class "AbstractArticle"'
'expression has type "CurrentSiteManager[CustomArticle]", base class "AbstractArticle"',
"Name 'Optional' is not defined",
],
'syndication_tests': [
'List or tuple expected as variable arguments'
],
'sessions_tests': [
'base class "SessionTestsMixin" defined the type as "None")',
'Incompatible types in assignment (expression has type "None", variable has type "int")',
'"AbstractBaseSession" has no attribute'
'"AbstractBaseSession" has no attribute',
'"None" not callable',
],
'select_related': [
'Item "ForeignKey[Union[Genus, Combinable], Genus]" '
@@ -324,18 +388,28 @@ IGNORED_ERRORS = {
re.compile('Argument [0-9] to "WSGIRequestHandler"'),
'"HTTPResponse" has no attribute',
'"type" has no attribute',
'"WSGIRequest" has no attribute "makefile"'
'"WSGIRequest" has no attribute "makefile"',
'LiveServerAddress',
'"Stub" has no attribute "makefile"',
],
'serializers': [
'"SerializersTestBase" defined the type as "None"',
'"Model" has no attribute "data"',
'"Iterable[Any]" has no attribute "content"',
re.compile(r'Argument 1 to "(serialize|deserialize)" has incompatible type "None"; expected "str"')
],
'string_lookup': [
'"Bar" has no attribute "place"',
],
'test_utils': [
'"PossessedCar" has no attribute "color"',
'expression has type "None", variable has type "List[str]"',
],
'transactions': [
'Incompatible types in assignment (expression has type "Thread", variable has type "Callable[[], Any]")'
],
'urlpatterns': [
'"object" not callable'
'"object" not callable',
'"None" not callable',
],
'urlpatterns_reverse': [
'List or tuple expected as variable arguments',
@@ -344,6 +418,7 @@ IGNORED_ERRORS = {
],
'utils_tests': [
'Argument 1 to "activate" has incompatible type "None"; expected "Union[tzinfo, str]"',
'Argument 1 to "activate" has incompatible type "Optional[str]"; expected "str"',
'Incompatible types in assignment (expression has type "None", base class "object" defined the type as',
'Class',
'has no attribute "cp"',
@@ -351,11 +426,28 @@ IGNORED_ERRORS = {
'has no attribute "sort"',
'Unsupported target for indexed assignment',
'defined the type as "None"',
'Argument 1 to "Path" has incompatible type "Optional[str]"'
'Argument 1 to "Path" has incompatible type "Optional[str]"',
'"None" not callable',
'"WSGIRequest" has no attribute "process_response_content"',
'No overload variant of "join" matches argument types "str", "None"',
'Argument 1 to "Archive" has incompatible type "None"; expected "str"',
'Argument 1 to "to_path" has incompatible type "int"; expected "Union[Path, str]"',
],
'view_tests': [
"Module 'django.views.debug' has no attribute 'Path'"
]
"Module 'django.views.debug' has no attribute 'Path'",
'Value of type "Optional[List[str]]" is not indexable',
'ExceptionUser',
'view_tests.tests.test_debug.User',
'Exception must be derived from BaseException',
"No binding for nonlocal 'tb_frames' found",
],
'validation': [
'has no attribute "name"',
],
'wsgi': [
'"HttpResponse" has no attribute "block_size"',
],
}

View File

@@ -2,15 +2,23 @@ import itertools
import shutil
import subprocess
import sys
from argparse import ArgumentParser
from collections import defaultdict
from pathlib import Path
from typing import Dict, List, Pattern, Union
from git import Repo
from scripts.enabled_test_modules import (
EXTERNAL_MODULES, IGNORED_ERRORS, IGNORED_MODULES, MOCK_OBJECTS,
)
DJANGO_COMMIT_REFS = {
'2.2': 'e8b0903976077b951795938b260211214ed7fe41',
'3.0': '7ec5962638144cbf4c2e47ea7d8dc02d1ce44394'
}
PROJECT_DIRECTORY = Path(__file__).parent.parent
DJANGO_SOURCE_DIRECTORY = PROJECT_DIRECTORY / 'django-sources' # type: Path
def get_unused_ignores(ignored_message_freq: Dict[str, Dict[Union[str, Pattern], int]]) -> List[str]:
@@ -67,11 +75,29 @@ def replace_with_clickable_location(error: str, abs_test_folder: Path) -> str:
return error.replace(raw_path, clickable_location)
def get_django_repo_object() -> Repo:
if not DJANGO_SOURCE_DIRECTORY.exists():
DJANGO_SOURCE_DIRECTORY.mkdir(exist_ok=True, parents=False)
return Repo.clone_from('https://github.com/django/django.git', DJANGO_SOURCE_DIRECTORY)
else:
repo = Repo(DJANGO_SOURCE_DIRECTORY)
return repo
if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument('--django_version', choices=['2.2', '3.0'], required=True)
args = parser.parse_args()
commit_sha = DJANGO_COMMIT_REFS[args.django_version]
repo = get_django_repo_object()
if repo.head.commit.hexsha != commit_sha:
repo.git.fetch('origin')
repo.git.checkout(commit_sha)
mypy_config_file = (PROJECT_DIRECTORY / 'scripts' / 'mypy.ini').absolute()
repo_directory = PROJECT_DIRECTORY / 'django-sources'
mypy_cache_dir = Path(__file__).parent / '.mypy_cache'
tests_root = repo_directory / 'tests'
tests_root = DJANGO_SOURCE_DIRECTORY / 'tests'
global_rc = 0
try:

View File

@@ -21,14 +21,14 @@ with open('README.md', 'r') as f:
readme = f.read()
dependencies = [
'mypy>=0.730',
'mypy>=0.750,<0.760',
'typing-extensions',
'django',
]
setup(
name="django-stubs",
version="1.2.0",
version="1.3.0",
description='Mypy stubs for Django',
long_description=readme,
long_description_content_type='text/markdown',

View File

@@ -99,7 +99,7 @@
class User(models.Model):
pass
class Profile(models.Model):
user = models.OneToOneField(to=User, on_delete=models)
user = models.OneToOneField(to=User, on_delete=models.CASCADE)
- case: test_circular_dependency_in_imports_with_foreign_key
main: |

View File

@@ -29,3 +29,19 @@
class Blog(models.Model):
created_at = models.DateTimeField()
- case: queryset_missing_method
main: |
from myapp.models import User
reveal_type(User.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.User]'
User.objects.not_existing_method() # E: "Manager[User]" has no attribute "not_existing_method"
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class User(models.Model):
pass

View File

@@ -33,7 +33,7 @@
- case: filter_with_invalid_type
main: |
from myapp.models import User
User.objects.filter(age='hello') # E: Incompatible type for lookup 'age': (got "str", expected "int")
User.objects.filter(age=User()) # E: Incompatible type for lookup 'age': (got "User", expected "Union[str, int]")
installed_apps:
- myapp
files:
@@ -49,12 +49,12 @@
- case: filter_with_multiple_fields
main: |
from myapp.models import User
User.objects.filter(age='hello', gender='world')
User.objects.filter(age=User(), gender=User())
installed_apps:
- myapp
out: |
main:2: error: Incompatible type for lookup 'age': (got "str", expected "int")
main:2: error: Incompatible type for lookup 'gender': (got "str", expected "int")
main:2: error: Incompatible type for lookup 'age': (got "User", expected "Union[str, int]")
main:2: error: Incompatible type for lookup 'gender': (got "User", expected "Union[str, int]")
files:
- path: myapp/__init__.py
- path: myapp/models.py
@@ -128,7 +128,7 @@
Blog.objects.filter(publisher__id=1)
Blog.objects.filter(publisher=blog) # E: Incompatible type for lookup 'publisher': (got "Blog", expected "Union[Publisher, int, None]")
Blog.objects.filter(publisher_id='hello') # E: Incompatible type for lookup 'publisher_id': (got "str", expected "int")
Blog.objects.filter(publisher_id=blog) # E: Incompatible type for lookup 'publisher_id': (got "Blog", expected "Union[str, int]")
installed_apps:
- myapp
files:
@@ -151,7 +151,7 @@
Publisher.objects.filter(blogs__id=1)
Publisher.objects.filter(blogs=publisher) # E: Incompatible type for lookup 'blogs': (got "Publisher", expected "Union[Blog, int, None]")
Publisher.objects.filter(blogs__id=publisher) # E: Incompatible type for lookup 'blogs__id': (got "Publisher", expected "int")
Publisher.objects.filter(blogs__id=publisher) # E: Incompatible type for lookup 'blogs__id': (got "Publisher", expected "Union[str, int]")
installed_apps:
- myapp
files:

View File

@@ -307,8 +307,18 @@
- case: custom_manager_returns_proper_model_types
main: |
from myapp.models import User
reveal_type(User.objects.get()) # N: Revealed type is 'myapp.models.User*'
reveal_type(User.objects) # N: Revealed type is 'myapp.models.User_MyManager[myapp.models.User]'
reveal_type(User.objects.select_related()) # N: Revealed type is 'myapp.models.User_MyManager[myapp.models.User]'
reveal_type(User.objects.get()) # N: Revealed type is 'myapp.models.User*'
reveal_type(User.objects.get_instance()) # N: Revealed type is 'builtins.int'
reveal_type(User.objects.get_instance_untyped('hello')) # N: Revealed type is 'Any'
from myapp.models import ChildUser
reveal_type(ChildUser.objects) # N: Revealed type is 'myapp.models.ChildUser_MyManager[myapp.models.ChildUser]'
reveal_type(ChildUser.objects.select_related()) # N: Revealed type is 'myapp.models.ChildUser_MyManager[myapp.models.ChildUser]'
reveal_type(ChildUser.objects.get()) # N: Revealed type is 'myapp.models.ChildUser*'
reveal_type(ChildUser.objects.get_instance()) # N: Revealed type is 'builtins.int'
reveal_type(ChildUser.objects.get_instance_untyped('hello')) # N: Revealed type is 'Any'
installed_apps:
- myapp
files:
@@ -317,6 +327,11 @@
content: |
from django.db import models
class MyManager(models.Manager):
pass
def get_instance(self) -> int:
pass
def get_instance_untyped(self, name):
pass
class User(models.Model):
objects = MyManager()
class ChildUser(models.Model):
objects = MyManager()