mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-14 15:57:08 +08:00
Compare commits
43 Commits
v1.3.0
...
newsemanal
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1419b144d9 | ||
|
|
0b1507c81e | ||
|
|
a01d58462e | ||
|
|
e5b61dc499 | ||
|
|
0a92c89d41 | ||
|
|
356a5881e7 | ||
|
|
90617bc76a | ||
|
|
13e40ab4a1 | ||
|
|
7ba578f6b2 | ||
|
|
cb123de105 | ||
|
|
38135f2d1f | ||
|
|
998b659749 | ||
|
|
72f69e1c5e | ||
|
|
d666ecd36f | ||
|
|
c1af26c027 | ||
|
|
3c3dfcbc9f | ||
|
|
1196336e3b | ||
|
|
665f4d8ea1 | ||
|
|
b3ed9e4827 | ||
|
|
fb1593630a | ||
|
|
031d42a75d | ||
|
|
f7e1cfc6c7 | ||
|
|
d0c25e3bce | ||
|
|
1c31e71ffc | ||
|
|
6b3b6be3c1 | ||
|
|
5832605053 | ||
|
|
31e795016f | ||
|
|
0cba3f9fd6 | ||
|
|
f02050911f | ||
|
|
e8e6fca78c | ||
|
|
ade48b6546 | ||
|
|
b8f29027d8 | ||
|
|
eba3f6cb15 | ||
|
|
5a45544e76 | ||
|
|
8c2de7da56 | ||
|
|
d43c6dc7e2 | ||
|
|
f7e2109e06 | ||
|
|
cea62abf5a | ||
|
|
3b69ec6a72 | ||
|
|
7e794534c0 | ||
|
|
f5f33b061d | ||
|
|
58b26fdbd3 | ||
|
|
9ca79c24a2 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ pip-wheel-metadata/
|
||||
.pytest_cache/
|
||||
/.envrc
|
||||
/.direnv
|
||||
django-sources/
|
||||
@@ -11,19 +11,16 @@ jobs:
|
||||
- name: Typecheck Django 3.0 test suite with python 3.7
|
||||
python: 3.7
|
||||
script: |
|
||||
pip install Django==3.0.*
|
||||
python ./scripts/typecheck_tests.py --django_version=3.0
|
||||
|
||||
- name: Typecheck Django 3.0 test suite with python 3.6
|
||||
python: 3.6
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
black
|
||||
pytest-mypy-plugins==1.1.0
|
||||
pytest-mypy-plugins==1.2.0
|
||||
psycopg2
|
||||
flake8==3.7.8
|
||||
flake8==3.7.9
|
||||
flake8-pyi==19.3.0
|
||||
isort==4.3.21
|
||||
gitpython==3.0.5
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import threading
|
||||
from collections import OrderedDict
|
||||
from typing import Any, Callable, DefaultDict, Dict, Iterable, List, Optional, Tuple, Type, Union
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type, Union
|
||||
|
||||
from django.db.migrations.state import AppConfigStub
|
||||
from django.db.models.base import Model
|
||||
|
||||
from .config import AppConfig
|
||||
|
||||
class Apps:
|
||||
all_models: Dict[str, OrderedDict[str, Type[Model]]] = ...
|
||||
app_configs: OrderedDict[str, AppConfig] = ...
|
||||
all_models: Dict[str, Dict[str, Type[Model]]] = ...
|
||||
app_configs: Dict[str, AppConfig] = ...
|
||||
stored_app_configs: List[Any] = ...
|
||||
apps_ready: bool = ...
|
||||
ready_event: threading.Event = ...
|
||||
loading: bool = ...
|
||||
_pending_operations: DefaultDict[Tuple[str, str], List]
|
||||
_pending_operations: Dict[Tuple[str, str], List]
|
||||
models_ready: bool = ...
|
||||
ready: bool = ...
|
||||
def __init__(self, installed_apps: Optional[Union[List[AppConfigStub], List[str], Tuple]] = ...) -> None: ...
|
||||
def populate(self, installed_apps: Union[List[AppConfigStub], List[str], Tuple] = ...) -> None: ...
|
||||
def __init__(self, installed_apps: Optional[Iterable[Union[AppConfig, str]]] = ...) -> None: ...
|
||||
def populate(self, installed_apps: Iterable[Union[AppConfig, str]] = ...) -> None: ...
|
||||
def check_apps_ready(self) -> None: ...
|
||||
def check_models_ready(self) -> None: ...
|
||||
def get_app_configs(self) -> Iterable[AppConfig]: ...
|
||||
@@ -31,9 +29,9 @@ class Apps:
|
||||
def get_containing_app_config(self, object_name: str) -> Optional[AppConfig]: ...
|
||||
def get_registered_model(self, app_label: str, model_name: str) -> Type[Model]: ...
|
||||
def get_swappable_settings_name(self, to_string: str) -> Optional[str]: ...
|
||||
def set_available_apps(self, available: List[str]) -> None: ...
|
||||
def set_available_apps(self, available: Iterable[str]) -> None: ...
|
||||
def unset_available_apps(self) -> None: ...
|
||||
def set_installed_apps(self, installed: Union[List[str], Tuple[str]]) -> None: ...
|
||||
def set_installed_apps(self, installed: Iterable[str]) -> None: ...
|
||||
def unset_installed_apps(self) -> None: ...
|
||||
def clear_cache(self) -> None: ...
|
||||
def lazy_model_operation(self, function: Callable, *model_keys: Any) -> None: ...
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
from typing import Any, List, Union
|
||||
from typing import Any, List, Union, Iterable, Optional
|
||||
|
||||
from django.contrib.admin.options import BaseModelAdmin
|
||||
from django.core.checks.messages import Error
|
||||
|
||||
from django.apps.config import AppConfig
|
||||
|
||||
_CheckError = Union[str, Error]
|
||||
|
||||
def check_admin_app(app_configs: None, **kwargs: Any) -> List[_CheckError]: ...
|
||||
def check_admin_app(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[_CheckError]: ...
|
||||
def check_dependencies(**kwargs: Any) -> List[_CheckError]: ...
|
||||
|
||||
class BaseModelAdminChecks:
|
||||
|
||||
@@ -8,6 +8,8 @@ from django.template.response import TemplateResponse
|
||||
from django.urls.resolvers import URLResolver
|
||||
from django.utils.functional import LazyObject
|
||||
|
||||
from django.apps.config import AppConfig
|
||||
|
||||
all_sites: Any
|
||||
|
||||
class AlreadyRegistered(Exception): ...
|
||||
@@ -28,7 +30,7 @@ class AdminSite:
|
||||
name: str = ...
|
||||
_registry: Dict[Type[Model], ModelAdmin]
|
||||
def __init__(self, name: str = ...) -> None: ...
|
||||
def check(self, app_configs: None) -> List[Any]: ...
|
||||
def check(self, app_configs: Optional[Iterable[AppConfig]]) -> List[Any]: ...
|
||||
def register(
|
||||
self,
|
||||
model_or_iterable: Union[Type[Model], Iterable[Type[Model]]],
|
||||
@@ -37,7 +39,7 @@ class AdminSite:
|
||||
) -> None: ...
|
||||
def unregister(self, model_or_iterable: Union[Type[Model], Iterable[Type[Model]]]) -> None: ...
|
||||
def is_registered(self, model: Type[Model]) -> bool: ...
|
||||
def add_action(self, action: Callable, name: None = ...) -> None: ...
|
||||
def add_action(self, action: Callable, name: Optional[str] = ...) -> None: ...
|
||||
def disable_action(self, name: str) -> None: ...
|
||||
def get_action(self, name: str) -> Callable: ...
|
||||
@property
|
||||
@@ -52,14 +54,20 @@ class AdminSite:
|
||||
@property
|
||||
def urls(self) -> Tuple[List[URLResolver], str, str]: ...
|
||||
def each_context(self, request: Any): ...
|
||||
def password_change(self, request: WSGIRequest, extra_context: Dict[str, str] = ...) -> TemplateResponse: ...
|
||||
def password_change_done(self, request: WSGIRequest, extra_context: None = ...) -> TemplateResponse: ...
|
||||
def password_change(
|
||||
self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...
|
||||
) -> TemplateResponse: ...
|
||||
def password_change_done(
|
||||
self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...
|
||||
) -> TemplateResponse: ...
|
||||
def i18n_javascript(self, request: WSGIRequest, extra_context: Optional[Dict[Any, Any]] = ...) -> HttpResponse: ...
|
||||
def logout(self, request: WSGIRequest, extra_context: None = ...) -> TemplateResponse: ...
|
||||
def login(self, request: WSGIRequest, extra_context: None = ...) -> HttpResponse: ...
|
||||
def logout(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> TemplateResponse: ...
|
||||
def login(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> HttpResponse: ...
|
||||
def get_app_list(self, request: WSGIRequest) -> List[Any]: ...
|
||||
def index(self, request: WSGIRequest, extra_context: Optional[Dict[str, str]] = ...) -> TemplateResponse: ...
|
||||
def app_index(self, request: WSGIRequest, app_label: str, extra_context: None = ...) -> TemplateResponse: ...
|
||||
def index(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> TemplateResponse: ...
|
||||
def app_index(
|
||||
self, request: WSGIRequest, app_label: str, extra_context: Optional[Dict[str, Any]] = ...
|
||||
) -> TemplateResponse: ...
|
||||
|
||||
class DefaultAdminSite(LazyObject): ...
|
||||
|
||||
|
||||
@@ -9,12 +9,10 @@ from django.contrib.auth.forms import AdminPasswordChangeForm
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
from django.db.models.base import Model
|
||||
from django.db.models.deletion import Collector
|
||||
from django.db.models.fields.mixins import FieldCacheMixin
|
||||
from django.db.models.fields.reverse_related import ManyToOneRel
|
||||
from django.db.models.options import Options
|
||||
from django.db.models.query import QuerySet
|
||||
from django.forms.forms import BaseForm
|
||||
from django.utils.safestring import SafeText
|
||||
|
||||
from django.db.models.fields import Field, reverse_related
|
||||
|
||||
@@ -27,7 +25,7 @@ def unquote(s: str) -> str: ...
|
||||
def flatten(fields: Any) -> List[Union[Callable, str]]: ...
|
||||
def flatten_fieldsets(fieldsets: Any) -> List[Union[Callable, str]]: ...
|
||||
def get_deleted_objects(
|
||||
objs: QuerySet, request: WSGIRequest, admin_site: AdminSite
|
||||
objs: Sequence[Optional[Model]], request: WSGIRequest, admin_site: AdminSite
|
||||
) -> Tuple[List[Any], Dict[Any, Any], Set[Any], List[Any]]: ...
|
||||
|
||||
class NestedObjects(Collector):
|
||||
@@ -41,22 +39,14 @@ class NestedObjects(Collector):
|
||||
model_objs: Any = ...
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
||||
def add_edge(self, source: Optional[Model], target: Model) -> None: ...
|
||||
def collect(
|
||||
self,
|
||||
objs: Union[Sequence[Optional[Model]], QuerySet],
|
||||
source: Optional[Type[Model]] = ...,
|
||||
source_attr: Optional[str] = ...,
|
||||
**kwargs: Any
|
||||
) -> None: ...
|
||||
def related_objects(self, related: ManyToOneRel, objs: List[Model]) -> QuerySet: ...
|
||||
def nested(self, format_callback: Callable = ...) -> Union[List[SafeText], List[int]]: ...
|
||||
def can_fast_delete(self, *args: Any, **kwargs: Any) -> bool: ...
|
||||
def related_objects(self, related: ManyToOneRel, objs: Sequence[Optional[Model]]) -> QuerySet: ...
|
||||
def nested(self, format_callback: Callable = ...) -> List[Any]: ...
|
||||
|
||||
def model_format_dict(obj: Any): ...
|
||||
def model_ngettext(obj: Union[Options, QuerySet], n: Optional[int] = ...) -> str: ...
|
||||
def lookup_field(
|
||||
name: Union[Callable, str], obj: Model, model_admin: BaseModelAdmin = ...
|
||||
) -> Tuple[Optional[Field], Callable, Callable]: ...
|
||||
) -> Tuple[Optional[Field], Any, Any]: ...
|
||||
def label_for_field(
|
||||
name: Union[Callable, str],
|
||||
model: Type[Model],
|
||||
@@ -65,16 +55,14 @@ def label_for_field(
|
||||
form: Optional[BaseForm] = ...,
|
||||
) -> Union[Tuple[Optional[str], Union[Callable, Type[str]]], str]: ...
|
||||
def help_text_for_field(name: str, model: Type[Model]) -> str: ...
|
||||
def display_for_field(
|
||||
value: Any, field: Union[Field, reverse_related.OneToOneRel], empty_value_display: str
|
||||
) -> str: ...
|
||||
def display_for_field(value: Any, field: Field, empty_value_display: str) -> str: ...
|
||||
def display_for_value(value: Any, empty_value_display: str, boolean: bool = ...) -> str: ...
|
||||
|
||||
class NotRelationField(Exception): ...
|
||||
|
||||
def get_model_from_relation(field: Union[Field, reverse_related.ForeignObjectRel]) -> Type[Model]: ...
|
||||
def reverse_field_path(model: Type[Model], path: str) -> Tuple[Type[Model], str]: ...
|
||||
def get_fields_from_path(model: Type[Model], path: str) -> List[Union[Field, FieldCacheMixin]]: ...
|
||||
def get_fields_from_path(model: Type[Model], path: str) -> List[Field]: ...
|
||||
def construct_change_message(
|
||||
form: AdminPasswordChangeForm, formsets: None, add: bool
|
||||
) -> List[Dict[str, Dict[str, List[str]]]]: ...
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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.contrib.auth.models import AnonymousUser, User, Permission
|
||||
|
||||
from django.db.models.base import Model
|
||||
|
||||
@@ -9,17 +9,26 @@ _AnyUser = Union[Model, AnonymousUser]
|
||||
|
||||
UserModel: Any
|
||||
|
||||
class ModelBackend:
|
||||
class BaseBackend:
|
||||
def authenticate(
|
||||
self, request: Any, username: Optional[str] = ..., password: Optional[str] = ..., **kwargs: Any
|
||||
) -> Optional[AbstractBaseUser]: ...
|
||||
def user_can_authenticate(self, user: Optional[_AnyUser]) -> bool: ...
|
||||
def get_user(self, user_id: int) -> Optional[AbstractBaseUser]: ...
|
||||
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: ...
|
||||
|
||||
class ModelBackend(BaseBackend):
|
||||
def has_module_perms(self, user_obj: _AnyUser, app_label: str) -> bool: ...
|
||||
def get_user(self, user_id: int) -> AbstractBaseUser: ...
|
||||
def user_can_authenticate(self, user: Optional[_AnyUser]) -> bool: ...
|
||||
def with_perm(
|
||||
self,
|
||||
perm: Union[str, Permission],
|
||||
is_active: bool = ...,
|
||||
include_superusers: bool = ...,
|
||||
obj: Optional[Model] = ...,
|
||||
): ...
|
||||
|
||||
class AllowAllUsersModelBackend(ModelBackend): ...
|
||||
|
||||
|
||||
@@ -13,11 +13,10 @@ class BaseUserManager(models.Manager[_T]):
|
||||
def get_by_natural_key(self, username: Optional[str]) -> _T: ...
|
||||
|
||||
class AbstractBaseUser(models.Model):
|
||||
REQUIRED_FIELDS: List[str] = ...
|
||||
|
||||
password = models.CharField(max_length=128)
|
||||
last_login = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
REQUIRED_FIELDS: List[str] = ...
|
||||
class Meta: ...
|
||||
def get_username(self) -> str: ...
|
||||
def natural_key(self) -> Tuple[str]: ...
|
||||
@property
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from typing import Any, List
|
||||
from typing import Any, List, Iterable, Optional
|
||||
|
||||
from django.core.checks.messages import CheckMessage
|
||||
|
||||
def check_user_model(app_configs: None = ..., **kwargs: Any) -> List[CheckMessage]: ...
|
||||
def check_models_permissions(app_configs: None = ..., **kwargs: Any) -> List[Any]: ...
|
||||
from django.apps.config import AppConfig
|
||||
|
||||
def check_user_model(app_configs: Optional[Iterable[AppConfig]] = ..., **kwargs: Any) -> List[CheckMessage]: ...
|
||||
def check_models_permissions(app_configs: Optional[Iterable[AppConfig]] = ..., **kwargs: Any) -> List[Any]: ...
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from typing import Any, Collection, Optional, Set, Tuple, Type, TypeVar, Union
|
||||
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
from django.contrib.auth.base_user import AbstractBaseUser as AbstractBaseUser, BaseUserManager as BaseUserManager
|
||||
from django.contrib.auth.validators import UnicodeUsernameValidator
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
@@ -43,11 +44,20 @@ class UserManager(BaseUserManager[_T]):
|
||||
def create_superuser(
|
||||
self, username: str, email: Optional[str], password: Optional[str], **extra_fields: Any
|
||||
) -> _T: ...
|
||||
def with_perm(
|
||||
self,
|
||||
perm: Union[str, Permission],
|
||||
is_active: bool = ...,
|
||||
include_superusers: bool = ...,
|
||||
backend: Optional[Union[Type[ModelBackend], str]] = ...,
|
||||
obj: Optional[Model] = ...,
|
||||
): ...
|
||||
|
||||
class PermissionsMixin(models.Model):
|
||||
is_superuser = models.BooleanField()
|
||||
groups = models.ManyToManyField(Group)
|
||||
user_permissions = models.ManyToManyField(Permission)
|
||||
def get_user_permissions(self, obj: Optional[_AnyUser] = ...) -> Set[str]: ...
|
||||
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: ...
|
||||
@@ -88,6 +98,7 @@ class AnonymousUser:
|
||||
def groups(self) -> EmptyManager: ...
|
||||
@property
|
||||
def user_permissions(self) -> EmptyManager: ...
|
||||
def get_user_permissions(self, obj: Optional[_AnyUser] = ...) -> Set[str]: ...
|
||||
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: ...
|
||||
|
||||
@@ -55,6 +55,7 @@ class PasswordResetDoneView(PasswordContextMixin, TemplateView):
|
||||
class PasswordResetConfirmView(PasswordContextMixin, FormView):
|
||||
post_reset_login: bool = ...
|
||||
post_reset_login_backend: Any = ...
|
||||
reset_url_token: str = ...
|
||||
title: Any = ...
|
||||
token_generator: Any = ...
|
||||
validlink: bool = ...
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from typing import Any, List
|
||||
from typing import Any, List, Iterable, Optional
|
||||
|
||||
def check_generic_foreign_keys(app_configs: None = ..., **kwargs: Any) -> List[Any]: ...
|
||||
def check_model_name_lengths(app_configs: None = ..., **kwargs: Any) -> List[Any]: ...
|
||||
from django.apps.config import AppConfig
|
||||
|
||||
def check_generic_foreign_keys(app_configs: Optional[Iterable[AppConfig]] = ..., **kwargs: Any) -> List[Any]: ...
|
||||
def check_model_name_lengths(app_configs: Optional[Iterable[AppConfig]] = ..., **kwargs: Any) -> List[Any]: ...
|
||||
|
||||
18
django-stubs/contrib/postgres/constraints.pyi
Normal file
18
django-stubs/contrib/postgres/constraints.pyi
Normal file
@@ -0,0 +1,18 @@
|
||||
from typing import Optional, Sequence, Tuple, Union
|
||||
|
||||
from django.db.models.constraints import BaseConstraint
|
||||
from django.db.models.expressions import Combinable
|
||||
from django.db.models.query_utils import Q
|
||||
|
||||
class ExclusionConstraint(BaseConstraint):
|
||||
expressions: Sequence[Tuple[Union[str, Combinable], str]]
|
||||
index_type: str
|
||||
condition: Optional[Q]
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
name: str,
|
||||
expressions: Sequence[Tuple[Union[str, Combinable], str]],
|
||||
condition: Optional[Q] = ...,
|
||||
index_type: Optional[str] = ...,
|
||||
): ...
|
||||
@@ -8,6 +8,8 @@ from .ranges import (
|
||||
FloatRangeField as FloatRangeField,
|
||||
DateRangeField as DateRangeField,
|
||||
DateTimeRangeField as DateTimeRangeField,
|
||||
RangeOperators as RangeOperators,
|
||||
RangeBoundary as RangeBoundary,
|
||||
)
|
||||
from .hstore import HStoreField as HStoreField
|
||||
from .citext import (
|
||||
|
||||
@@ -29,3 +29,20 @@ class DateTimeRangeField(RangeField):
|
||||
|
||||
class DateRangeField(RangeField):
|
||||
def __get__(self, instance, owner) -> DateRange: ...
|
||||
|
||||
class RangeOperators:
|
||||
EQUAL: str
|
||||
NOT_EQUAL: str
|
||||
CONTAINS: str
|
||||
CONTAINED_BY: str
|
||||
OVERLAPS: str
|
||||
FULLY_LT: str
|
||||
FULLY_GT: str
|
||||
NOT_LT: str
|
||||
NOT_GT: str
|
||||
ADJACENT_TO: str
|
||||
|
||||
class RangeBoundary(models.Expression):
|
||||
lower: str
|
||||
upper: str
|
||||
def __init__(self, inclusive_lower: bool = ..., inclusive_upper: bool = ...): ...
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from typing import Any
|
||||
|
||||
from django.apps.config import AppConfig
|
||||
from django.apps.registry import Apps
|
||||
from django.contrib.sites.apps import SitesConfig
|
||||
|
||||
def create_default_site(
|
||||
app_config: SitesConfig,
|
||||
app_config: AppConfig,
|
||||
verbosity: int = ...,
|
||||
interactive: bool = ...,
|
||||
using: str = ...,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from typing import Any, List
|
||||
from typing import Any, List, Iterable, Optional
|
||||
|
||||
from django.core.checks.messages import Error
|
||||
|
||||
def check_finders(app_configs: None = ..., **kwargs: Any) -> List[Error]: ...
|
||||
from django.apps.config import AppConfig
|
||||
|
||||
def check_finders(app_configs: Optional[Iterable[AppConfig]] = ..., **kwargs: Any) -> List[Error]: ...
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from typing import Any, List
|
||||
from typing import Any, List, Iterable, Optional
|
||||
|
||||
from django.core.checks.messages import Error
|
||||
|
||||
from django.apps.config import AppConfig
|
||||
|
||||
E001: Any
|
||||
|
||||
def check_default_cache_is_configured(app_configs: None, **kwargs: Any) -> List[Error]: ...
|
||||
def check_default_cache_is_configured(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Error]: ...
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from typing import Any, List
|
||||
from typing import Any, List, Iterable, Optional
|
||||
|
||||
from django.core.checks.messages import Warning
|
||||
|
||||
def check_all_models(app_configs: None = ..., **kwargs: Any) -> List[Warning]: ...
|
||||
def check_lazy_references(app_configs: None = ..., **kwargs: Any) -> List[Any]: ...
|
||||
from django.apps.config import AppConfig
|
||||
|
||||
def check_all_models(app_configs: Optional[Iterable[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...
|
||||
def check_lazy_references(app_configs: Optional[Iterable[AppConfig]] = ..., **kwargs: Any) -> List[Any]: ...
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from typing import Any, List
|
||||
from typing import Any, List, Iterable, Optional
|
||||
|
||||
from django.core.checks.messages import Warning
|
||||
|
||||
from django.apps.config import AppConfig
|
||||
|
||||
SECRET_KEY_MIN_LENGTH: int
|
||||
SECRET_KEY_MIN_UNIQUE_CHARACTERS: int
|
||||
W001: Any
|
||||
@@ -17,15 +19,15 @@ W019: Any
|
||||
W020: Any
|
||||
W021: Any
|
||||
|
||||
def check_security_middleware(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_xframe_options_middleware(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_sts(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_sts_include_subdomains(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_sts_preload(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_content_type_nosniff(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_xss_filter(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_ssl_redirect(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_secret_key(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_debug(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_xframe_deny(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_allowed_hosts(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_security_middleware(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
def check_xframe_options_middleware(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
def check_sts(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
def check_sts_include_subdomains(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
def check_sts_preload(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
def check_content_type_nosniff(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
def check_xss_filter(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
def check_ssl_redirect(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
def check_secret_key(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
def check_debug(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
def check_xframe_deny(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
def check_allowed_hosts(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
from typing import Any, List
|
||||
from typing import Any, List, Iterable, Optional
|
||||
|
||||
from django.core.checks.messages import Warning
|
||||
|
||||
from django.apps.config import AppConfig
|
||||
|
||||
W003: Any
|
||||
W016: Any
|
||||
|
||||
def check_csrf_middleware(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_csrf_cookie_secure(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_csrf_middleware(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
def check_csrf_cookie_secure(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from typing import Any, List
|
||||
from typing import Any, List, Iterable, Optional
|
||||
|
||||
from django.core.checks.messages import Warning
|
||||
|
||||
from django.apps.config import AppConfig
|
||||
|
||||
def add_session_cookie_message(message: Any): ...
|
||||
|
||||
W010: Any
|
||||
@@ -14,5 +16,5 @@ W013: Any
|
||||
W014: Any
|
||||
W015: Any
|
||||
|
||||
def check_session_cookie_secure(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_session_cookie_httponly(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_session_cookie_secure(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
def check_session_cookie_httponly(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
from typing import Any, List
|
||||
from typing import Any, List, Iterable, Optional
|
||||
|
||||
from django.core.checks.messages import Error
|
||||
|
||||
from django.apps.config import AppConfig
|
||||
|
||||
E001: Any
|
||||
E002: Any
|
||||
|
||||
def check_setting_app_dirs_loaders(app_configs: None, **kwargs: Any) -> List[Error]: ...
|
||||
def check_string_if_invalid_is_string(app_configs: None, **kwargs: Any) -> List[Error]: ...
|
||||
def check_setting_app_dirs_loaders(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Error]: ...
|
||||
def check_string_if_invalid_is_string(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Error]: ...
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
from typing import Any, Callable, List, Tuple, Union
|
||||
from typing import Any, Callable, List, Tuple, Union, Iterable, Optional
|
||||
|
||||
from django.core.checks.messages import CheckMessage, Error, Warning
|
||||
from django.urls.resolvers import URLPattern, URLResolver
|
||||
|
||||
def check_url_config(app_configs: None, **kwargs: Any) -> List[CheckMessage]: ...
|
||||
from django.apps.config import AppConfig
|
||||
|
||||
def check_url_config(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[CheckMessage]: ...
|
||||
def check_resolver(resolver: Union[Tuple[str, Callable], URLPattern, URLResolver]) -> List[CheckMessage]: ...
|
||||
def check_url_namespaces_unique(app_configs: None, **kwargs: Any) -> List[Warning]: ...
|
||||
def check_url_namespaces_unique(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ...
|
||||
def get_warning_for_invalid_pattern(pattern: Any) -> List[Error]: ...
|
||||
def check_url_settings(app_configs: None, **kwargs: Any) -> List[Error]: ...
|
||||
def check_url_settings(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Error]: ...
|
||||
def E006(name: str) -> Error: ...
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
def supports_color() -> bool: ...
|
||||
|
||||
class Style:
|
||||
def DEBUG(self, text: str) -> str: ...
|
||||
def INFO(self, text: str) -> str: ...
|
||||
def ERROR(self, text: str) -> str: ...
|
||||
def SUCCESS(self, text: str) -> str: ...
|
||||
def WARNING(self, text: str) -> str: ...
|
||||
def ERROR(self, text: str) -> str: ...
|
||||
def NOTICE(self, text: str) -> str: ...
|
||||
def SQL_FIELD(self, text: str) -> str: ...
|
||||
def SQL_COLTYPE(self, text: str) -> str: ...
|
||||
def SQL_KEYWORD(self, text: str) -> str: ...
|
||||
def SQL_TABLE(self, text: str) -> str: ...
|
||||
def HTTP_INFO(self, text: str) -> str: ...
|
||||
def HTTP_SUCCESS(self, text: str) -> str: ...
|
||||
def HTTP_REDIRECT(self, text: str) -> str: ...
|
||||
def HTTP_NOT_MODIFIED(self, text: str) -> str: ...
|
||||
def HTTP_BAD_REQUEST(self, text: str) -> str: ...
|
||||
def HTTP_NOT_FOUND(self, text: str) -> str: ...
|
||||
def HTTP_SERVER_ERROR(self, text: str) -> str: ...
|
||||
def MIGRATE_HEADING(self, text: str) -> str: ...
|
||||
def MIGRATE_LABEL(self, text: str) -> str: ...
|
||||
def ERROR_OUTPUT(self, text: str) -> str: ...
|
||||
|
||||
def make_style(config_string: str = ...) -> Style: ...
|
||||
def no_style() -> Style: ...
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from datetime import date
|
||||
from io import BufferedReader, StringIO, TextIOWrapper
|
||||
from typing import Any, Dict, Iterable, List, Mapping, Optional, Type, Union
|
||||
from typing import Any, Dict, Iterable, List, Mapping, Optional, Type, Union, Collection
|
||||
from uuid import UUID
|
||||
|
||||
from django.core.management.base import OutputWrapper
|
||||
@@ -35,18 +35,18 @@ class Serializer:
|
||||
internal_use_only: bool = ...
|
||||
progress_class: Any = ...
|
||||
stream_class: Any = ...
|
||||
options: Any = ...
|
||||
options: Dict[str, Any] = ...
|
||||
stream: Any = ...
|
||||
selected_fields: Any = ...
|
||||
use_natural_foreign_keys: Any = ...
|
||||
use_natural_primary_keys: Any = ...
|
||||
selected_fields: Optional[Collection[str]] = ...
|
||||
use_natural_foreign_keys: bool = ...
|
||||
use_natural_primary_keys: bool = ...
|
||||
first: bool = ...
|
||||
def serialize(
|
||||
self,
|
||||
queryset: Iterable[Model],
|
||||
*,
|
||||
stream: Optional[Any] = ...,
|
||||
fields: Optional[Any] = ...,
|
||||
fields: Optional[Collection[str]] = ...,
|
||||
use_natural_foreign_keys: bool = ...,
|
||||
use_natural_primary_keys: bool = ...,
|
||||
progress_output: Optional[Any] = ...,
|
||||
@@ -63,7 +63,7 @@ class Serializer:
|
||||
def getvalue(self) -> Optional[Union[bytes, str]]: ...
|
||||
|
||||
class Deserializer:
|
||||
options: Any = ...
|
||||
options: Dict[str, Any] = ...
|
||||
stream: Any = ...
|
||||
def __init__(self, stream_or_string: Union[BufferedReader, TextIOWrapper, str], **options: Any) -> None: ...
|
||||
def __iter__(self) -> Deserializer: ...
|
||||
|
||||
@@ -1,24 +1,10 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from io import TextIOWrapper
|
||||
from typing import Any, Union, Dict
|
||||
from uuid import UUID
|
||||
from typing import Any, Dict
|
||||
|
||||
from django.core.serializers.python import Serializer as PythonSerializer
|
||||
from django.db.models.base import Model
|
||||
|
||||
class Serializer(PythonSerializer):
|
||||
json_kwargs: Dict[str, Any]
|
||||
options: Dict[str, None]
|
||||
selected_fields: None
|
||||
stream: TextIOWrapper
|
||||
use_natural_foreign_keys: bool
|
||||
use_natural_primary_keys: bool
|
||||
internal_use_only: bool = ...
|
||||
def start_serialization(self) -> None: ...
|
||||
def end_serialization(self) -> None: ...
|
||||
def end_object(self, obj: Model) -> None: ...
|
||||
|
||||
def Deserializer(stream_or_string: Any, **options: Any) -> None: ...
|
||||
|
||||
@@ -29,4 +15,3 @@ class DjangoJSONEncoder(json.JSONEncoder):
|
||||
indent: int
|
||||
skipkeys: bool
|
||||
sort_keys: bool
|
||||
def default(self, o: Union[datetime, Decimal, UUID]) -> str: ...
|
||||
|
||||
@@ -1,31 +1,15 @@
|
||||
from collections import OrderedDict
|
||||
from io import TextIOWrapper
|
||||
from typing import Any, Dict, Iterator, List
|
||||
from typing import Any, Dict, Iterator, List, Optional
|
||||
|
||||
from django.core.serializers.base import DeserializedObject
|
||||
from django.db.models.base import Model
|
||||
from django.db.models.fields.related import ForeignKey, ManyToManyField
|
||||
|
||||
from django.core.serializers import base
|
||||
from django.db.models.fields import Field
|
||||
|
||||
class Serializer(base.Serializer):
|
||||
options: Dict[Any, Any]
|
||||
selected_fields: None
|
||||
stream: TextIOWrapper
|
||||
use_natural_foreign_keys: bool
|
||||
use_natural_primary_keys: bool
|
||||
internal_use_only: bool = ...
|
||||
objects: List[Any] = ...
|
||||
def start_serialization(self) -> None: ...
|
||||
def end_serialization(self) -> None: ...
|
||||
def start_object(self, obj: Model) -> None: ...
|
||||
def end_object(self, obj: Model) -> None: ...
|
||||
def get_dump_object(self, obj: Model) -> OrderedDict: ...
|
||||
def handle_field(self, obj: Model, field: Field) -> None: ...
|
||||
def handle_fk_field(self, obj: Model, field: ForeignKey) -> None: ...
|
||||
def handle_m2m_field(self, obj: Model, field: ManyToManyField) -> None: ...
|
||||
|
||||
def Deserializer(
|
||||
object_list: List[Dict[str, Any]], *, using: Any = ..., ignorenonexistent: bool = ..., **options: Any
|
||||
object_list: List[Dict[str, Any]], *, using: Optional[str] = ..., ignorenonexistent: bool = ..., **options: Any
|
||||
) -> Iterator[DeserializedObject]: ...
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from re import RegexFlag
|
||||
from typing import Any, Dict, List, Optional, Union, Pattern, Collection
|
||||
from uuid import UUID
|
||||
from typing import Any, Callable, Collection, Dict, List, Optional, Pattern, Tuple, Union
|
||||
|
||||
from django.core.files.base import File
|
||||
from django.core.exceptions import ValidationError as ValidationError # noqa: F401
|
||||
|
||||
EMPTY_VALUES: Any
|
||||
|
||||
_Regex = Union[str, Pattern[str]]
|
||||
_ErrorMessage = Union[str, Any]
|
||||
|
||||
def _lazy_re_compile(regex: _Regex, flags: int = ...): ...
|
||||
|
||||
class RegexValidator:
|
||||
regex: _Regex = ...
|
||||
message: Any = ...
|
||||
message: str = ...
|
||||
code: str = ...
|
||||
inverse_match: bool = ...
|
||||
flags: int = ...
|
||||
def __init__(
|
||||
self,
|
||||
regex: Optional[_Regex] = ...,
|
||||
message: Optional[str] = ...,
|
||||
message: Optional[_ErrorMessage] = ...,
|
||||
code: Optional[str] = ...,
|
||||
inverse_match: Optional[bool] = ...,
|
||||
flags: Optional[RegexFlag] = ...,
|
||||
@@ -33,95 +31,82 @@ class URLValidator(RegexValidator):
|
||||
ul: str = ...
|
||||
ipv4_re: str = ...
|
||||
ipv6_re: str = ...
|
||||
hostname_re: Any = ...
|
||||
domain_re: Any = ...
|
||||
tld_re: Any = ...
|
||||
host_re: Any = ...
|
||||
schemes: Any = ...
|
||||
hostname_re: str = ...
|
||||
domain_re: str = ...
|
||||
tld_re: str = ...
|
||||
host_re: str = ...
|
||||
schemes: List[str] = ...
|
||||
def __init__(self, schemes: Optional[Collection[str]] = ..., **kwargs: Any) -> None: ...
|
||||
|
||||
integer_validator: Any
|
||||
integer_validator: RegexValidator = ...
|
||||
|
||||
def validate_integer(value: Optional[Union[float, str]]) -> None: ...
|
||||
|
||||
class EmailValidator:
|
||||
message: Any = ...
|
||||
message: str = ...
|
||||
code: str = ...
|
||||
user_regex: Any = ...
|
||||
domain_regex: Any = ...
|
||||
literal_regex: Any = ...
|
||||
domain_whitelist: Any = ...
|
||||
user_regex: Pattern = ...
|
||||
domain_regex: Pattern = ...
|
||||
literal_regex: Pattern = ...
|
||||
domain_whitelist: List[str] = ...
|
||||
def __init__(
|
||||
self, message: Optional[str] = ..., code: Optional[str] = ..., whitelist: Optional[Collection[str]] = ...
|
||||
self,
|
||||
message: Optional[_ErrorMessage] = ...,
|
||||
code: Optional[str] = ...,
|
||||
whitelist: Optional[Collection[str]] = ...,
|
||||
) -> None: ...
|
||||
def __call__(self, value: Optional[str]) -> None: ...
|
||||
def validate_domain_part(self, domain_part: str) -> bool: ...
|
||||
|
||||
validate_email: Any
|
||||
slug_re: Any
|
||||
validate_slug: Any
|
||||
slug_unicode_re: Any
|
||||
validate_unicode_slug: Any
|
||||
validate_email: EmailValidator = ...
|
||||
slug_re: Pattern = ...
|
||||
validate_slug: RegexValidator = ...
|
||||
slug_unicode_re: Pattern = ...
|
||||
validate_unicode_slug: RegexValidator = ...
|
||||
|
||||
def validate_ipv4_address(value: str) -> None: ...
|
||||
def validate_ipv6_address(value: str) -> None: ...
|
||||
def validate_ipv46_address(value: str) -> None: ...
|
||||
|
||||
ip_address_validator_map: Any
|
||||
_IPValidator = Tuple[Callable[[Any], None], str]
|
||||
ip_address_validator_map: Dict[str, _IPValidator]
|
||||
|
||||
def ip_address_validators(protocol: str, unpack_ipv4: bool) -> Any: ...
|
||||
def ip_address_validators(protocol: str, unpack_ipv4: bool) -> _IPValidator: ...
|
||||
def int_list_validator(
|
||||
sep: str = ..., message: None = ..., code: str = ..., allow_negative: bool = ...
|
||||
sep: str = ..., message: Optional[_ErrorMessage] = ..., code: str = ..., allow_negative: bool = ...
|
||||
) -> RegexValidator: ...
|
||||
|
||||
validate_comma_separated_integer_list: Any
|
||||
|
||||
class BaseValidator:
|
||||
message: Any = ...
|
||||
message: str = ...
|
||||
code: str = ...
|
||||
limit_value: Any = ...
|
||||
def __init__(self, limit_value: Any, message: Optional[str] = ...) -> None: ...
|
||||
def __init__(self, limit_value: Any, message: Optional[_ErrorMessage] = ...) -> None: ...
|
||||
def __call__(self, value: Any) -> None: ...
|
||||
def compare(self, a: bool, b: bool) -> bool: ...
|
||||
def compare(self, a: Any, b: Any) -> bool: ...
|
||||
def clean(self, x: Any) -> Any: ...
|
||||
|
||||
class MaxValueValidator(BaseValidator):
|
||||
message: Any = ...
|
||||
code: str = ...
|
||||
def compare(self, a: Union[datetime, Decimal, float], b: Union[datetime, Decimal, float]) -> bool: ...
|
||||
|
||||
class MinValueValidator(BaseValidator):
|
||||
message: Any = ...
|
||||
code: str = ...
|
||||
def compare(self, a: Union[datetime, Decimal, float], b: Union[datetime, Decimal, float]) -> bool: ...
|
||||
|
||||
class MinLengthValidator(BaseValidator):
|
||||
message: Any = ...
|
||||
code: str = ...
|
||||
def compare(self, a: int, b: int) -> bool: ...
|
||||
def clean(self, x: str) -> int: ...
|
||||
|
||||
class MaxLengthValidator(BaseValidator):
|
||||
message: Any = ...
|
||||
code: str = ...
|
||||
def compare(self, a: int, b: int) -> bool: ...
|
||||
def clean(self, x: Union[bytes, str]) -> int: ...
|
||||
class MaxValueValidator(BaseValidator): ...
|
||||
class MinValueValidator(BaseValidator): ...
|
||||
class MinLengthValidator(BaseValidator): ...
|
||||
class MaxLengthValidator(BaseValidator): ...
|
||||
|
||||
class DecimalValidator:
|
||||
messages: Any = ...
|
||||
messages: Dict[str, str] = ...
|
||||
max_digits: int = ...
|
||||
decimal_places: int = ...
|
||||
def __init__(self, max_digits: Optional[Union[int, str]], decimal_places: Optional[Union[int, str]]) -> None: ...
|
||||
def __call__(self, value: Decimal) -> None: ...
|
||||
|
||||
class FileExtensionValidator:
|
||||
message: Any = ...
|
||||
message: str = ...
|
||||
code: str = ...
|
||||
allowed_extensions: List[str] = ...
|
||||
def __init__(
|
||||
self,
|
||||
allowed_extensions: Optional[Collection[str]] = ...,
|
||||
message: Optional[str] = ...,
|
||||
message: Optional[_ErrorMessage] = ...,
|
||||
code: Optional[str] = ...,
|
||||
) -> None: ...
|
||||
def __call__(self, value: File) -> None: ...
|
||||
@@ -130,7 +115,7 @@ def get_available_image_extensions() -> List[str]: ...
|
||||
def validate_image_file_extension(value: File) -> None: ...
|
||||
|
||||
class ProhibitNullCharactersValidator:
|
||||
message: Any = ...
|
||||
message: str = ...
|
||||
code: str = ...
|
||||
def __init__(self, message: Optional[str] = ..., code: Optional[str] = ...) -> None: ...
|
||||
def __call__(self, value: Optional[Union[Dict[Any, Any], str, UUID]]) -> None: ...
|
||||
def __init__(self, message: Optional[_ErrorMessage] = ..., code: Optional[str] = ...) -> None: ...
|
||||
def __call__(self, value: Any) -> None: ...
|
||||
|
||||
@@ -10,3 +10,5 @@ class DatabaseWrapper(BaseDatabaseWrapper): ...
|
||||
FORMAT_QMARK_REGEX: Any
|
||||
|
||||
class SQLiteCursorWrapper(Database.Cursor): ...
|
||||
|
||||
def check_sqlite_version() -> None: ...
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
from typing import Any, DefaultDict, Dict, Iterator, List, Optional, Sequence, Tuple, Type, Union, Set
|
||||
from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple, Type, Union, Set
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.apps.registry import Apps
|
||||
from django.db.models.base import Model
|
||||
from django.db.models.manager import Manager
|
||||
|
||||
from django.db.models.fields import Field
|
||||
|
||||
class AppConfigStub:
|
||||
apps: None
|
||||
label: str
|
||||
models: None
|
||||
models_module: None
|
||||
module: None
|
||||
name: str
|
||||
verbose_name: str
|
||||
def __init__(self, label: str) -> None: ...
|
||||
def import_models(self) -> None: ...
|
||||
class AppConfigStub(AppConfig): ...
|
||||
|
||||
class ModelState:
|
||||
name: str
|
||||
@@ -66,13 +58,7 @@ class ProjectState:
|
||||
def remove_model(self, app_label: str, model_name: str) -> None: ...
|
||||
|
||||
class StateApps(Apps):
|
||||
all_models: DefaultDict
|
||||
apps_ready: bool
|
||||
loading: bool
|
||||
models_ready: bool
|
||||
ready: bool
|
||||
real_models: List[ModelState]
|
||||
stored_app_configs: List[Any]
|
||||
def __init__(
|
||||
self, real_apps: List[str], models: Dict[Tuple[str, str], ModelState], ignore_swappable: bool = ...
|
||||
) -> None: ...
|
||||
|
||||
@@ -2,7 +2,7 @@ from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Ty
|
||||
|
||||
from django.core.checks.messages import CheckMessage
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models.manager import Manager
|
||||
from django.db.models.manager import BaseManager
|
||||
from django.db.models.options import Options
|
||||
|
||||
_Self = TypeVar("_Self", bound="Model")
|
||||
@@ -13,9 +13,9 @@ class Model(metaclass=ModelBase):
|
||||
class DoesNotExist(Exception): ...
|
||||
class MultipleObjectsReturned(Exception): ...
|
||||
class Meta: ...
|
||||
_default_manager: Manager[Model]
|
||||
_meta: Options[Any]
|
||||
objects: Manager[Any]
|
||||
_default_manager: BaseManager[Model]
|
||||
objects: BaseManager[Any]
|
||||
pk: Any = ...
|
||||
def __init__(self: _Self, *args, **kwargs) -> None: ...
|
||||
def delete(self, using: Any = ..., keep_parents: bool = ...) -> Tuple[int, Dict[str, int]]: ...
|
||||
@@ -41,7 +41,7 @@ class Model(metaclass=ModelBase):
|
||||
using: Optional[str] = ...,
|
||||
update_fields: Optional[Union[Sequence[str], str]] = ...,
|
||||
): ...
|
||||
def refresh_from_db(self: _Self, using: Optional[str] = ..., fields: Optional[List[str]] = ...) -> _Self: ...
|
||||
def refresh_from_db(self: _Self, using: Optional[str] = ..., fields: Optional[List[str]] = ...) -> None: ...
|
||||
def get_deferred_fields(self) -> Set[str]: ...
|
||||
@classmethod
|
||||
def check(cls, **kwargs: Any) -> List[CheckMessage]: ...
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Callable, Iterable, Optional, Union
|
||||
from typing import Any, Callable, Iterable, Optional, Union, Collection, Type
|
||||
|
||||
from django.db.models.base import Model
|
||||
|
||||
@@ -18,4 +18,11 @@ class ProtectedError(IntegrityError): ...
|
||||
|
||||
class Collector:
|
||||
def __init__(self, using: str) -> None: ...
|
||||
def collect(
|
||||
self,
|
||||
objs: Collection[Optional[Model]],
|
||||
source: Optional[Type[Model]] = ...,
|
||||
source_attr: Optional[str] = ...,
|
||||
**kwargs: Any
|
||||
) -> None: ...
|
||||
def can_fast_delete(self, objs: Union[Model, Iterable[Model]], from_field: Optional[Field] = ...) -> bool: ...
|
||||
|
||||
@@ -27,6 +27,8 @@ from django.forms import Field as FormField, Widget
|
||||
|
||||
class NOT_PROVIDED: ...
|
||||
|
||||
BLANK_CHOICE_DASH: List[Tuple[str, str]] = ...
|
||||
|
||||
_Choice = Tuple[Any, Any]
|
||||
_ChoiceNamedGroup = Tuple[str, Iterable[_Choice]]
|
||||
_FieldChoices = Iterable[Union[_Choice, _ChoiceNamedGroup]]
|
||||
@@ -40,6 +42,9 @@ _ST = TypeVar("_ST")
|
||||
# __get__ return type
|
||||
_GT = TypeVar("_GT")
|
||||
|
||||
class CharField(Field[str, str]):
|
||||
|
||||
|
||||
class Field(RegisterLookupMixin, Generic[_ST, _GT]):
|
||||
_pyi_private_set_type: Any
|
||||
_pyi_private_get_type: Any
|
||||
@@ -63,7 +68,7 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
|
||||
null: bool = ...
|
||||
editable: bool = ...
|
||||
empty_strings_allowed: bool = ...
|
||||
choices: Optional[_FieldChoices] = ...
|
||||
choices: _FieldChoices = ...
|
||||
db_column: Optional[str]
|
||||
column: str
|
||||
default: Any
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Iterable, List, Optional, Tuple, Type, TypeVar, Union, overload
|
||||
from typing import Any, Callable, Iterable, Optional, Type, TypeVar, Union, overload
|
||||
|
||||
from django.core.files.base import File
|
||||
from django.core.files.images import ImageFile
|
||||
@@ -8,8 +8,6 @@ from django.db.models.base import Model
|
||||
|
||||
from django.db.models.fields import Field, _FieldChoices, _ValidatorCallable, _ErrorMessagesToOverride
|
||||
|
||||
BLANK_CHOICE_DASH: List[Tuple[str, str]] = ...
|
||||
|
||||
class FieldFile(File):
|
||||
instance: Model = ...
|
||||
field: FileField = ...
|
||||
|
||||
@@ -14,9 +14,14 @@ from .text import (
|
||||
Trim as Trim,
|
||||
Ord as Ord,
|
||||
Repeat as Repeat,
|
||||
SHA1 as SHA1,
|
||||
SHA224 as SHA224,
|
||||
SHA256 as SHA256,
|
||||
SHA384 as SHA384,
|
||||
SHA512 as SHA512,
|
||||
StrIndex as StrIndex,
|
||||
Replace as Replace,
|
||||
Substr as Substr,
|
||||
Replace as Replace,
|
||||
Reverse as Reverse,
|
||||
)
|
||||
|
||||
@@ -81,6 +86,7 @@ from .math import (
|
||||
Power as Power,
|
||||
Radians as Radians,
|
||||
Round as Round,
|
||||
Sign as Sign,
|
||||
Sin as Sin,
|
||||
Sqrt as Sqrt,
|
||||
Tan as Tan,
|
||||
|
||||
@@ -23,3 +23,4 @@ class Round(Transform): ...
|
||||
class Sin(NumericOutputFieldMixin, Transform): ...
|
||||
class Sqrt(NumericOutputFieldMixin, Transform): ...
|
||||
class Tan(NumericOutputFieldMixin, Transform): ...
|
||||
class Sign(Transform): ...
|
||||
|
||||
@@ -55,3 +55,11 @@ class Substr(Func):
|
||||
class Trim(Transform): ...
|
||||
class Upper(Transform): ...
|
||||
class Reverse(Transform): ...
|
||||
class MySQLSHA2Mixin: ...
|
||||
class OracleHashMixin: ...
|
||||
class PostgreSQLSHAMixin: ...
|
||||
class SHA1(OracleHashMixin, PostgreSQLSHAMixin, Transform): ...
|
||||
class SHA224(MySQLSHA2Mixin, PostgreSQLSHAMixin, Transform): ...
|
||||
class SHA256(MySQLSHA2Mixin, OracleHashMixin, PostgreSQLSHAMixin, Transform): ...
|
||||
class SHA384(MySQLSHA2Mixin, OracleHashMixin, PostgreSQLSHAMixin, Transform): ...
|
||||
class SHA512(MySQLSHA2Mixin, OracleHashMixin, PostgreSQLSHAMixin, Transform): ...
|
||||
|
||||
@@ -8,6 +8,7 @@ from django.contrib.postgres.fields.array import ArrayField
|
||||
from django.contrib.postgres.fields.citext import CIText
|
||||
from django.db.backends.sqlite3.base import DatabaseWrapper
|
||||
from django.db.models.base import Model
|
||||
from django.db.models.constraints import BaseConstraint
|
||||
from django.db.models.fields.mixins import FieldCacheMixin
|
||||
from django.db.models.fields.related import ManyToManyField, OneToOneField
|
||||
from django.db.models.fields.reverse_related import ForeignObjectRel
|
||||
@@ -34,6 +35,7 @@ _M = TypeVar("_M", bound=Model)
|
||||
class Options(Generic[_M]):
|
||||
base_manager: Manager
|
||||
concrete_fields: ImmutableList
|
||||
constraints: List[BaseConstraint]
|
||||
default_manager: Manager
|
||||
fields: ImmutableList
|
||||
local_concrete_fields: ImmutableList
|
||||
|
||||
@@ -16,6 +16,7 @@ from typing import (
|
||||
TypeVar,
|
||||
Union,
|
||||
overload,
|
||||
Reversible,
|
||||
)
|
||||
|
||||
from django.db.models.base import Model
|
||||
@@ -30,6 +31,7 @@ _T = TypeVar("_T", bound=models.Model, covariant=True)
|
||||
_QS = TypeVar("_QS", bound="_BaseQuerySet")
|
||||
|
||||
class _BaseQuerySet(Generic[_T], Sized):
|
||||
model: Type[_T]
|
||||
query: Query
|
||||
def __init__(
|
||||
self,
|
||||
@@ -121,13 +123,14 @@ class _BaseQuerySet(Generic[_T], Sized):
|
||||
def db(self) -> str: ...
|
||||
def resolve_expression(self, *args: Any, **kwargs: Any) -> Any: ...
|
||||
|
||||
class QuerySet(_BaseQuerySet[_T], Collection[_T], Sized):
|
||||
class QuerySet(_BaseQuerySet[_T], Collection[_T], Reversible[_T], Sized):
|
||||
def __iter__(self) -> Iterator[_T]: ...
|
||||
def __contains__(self, x: object) -> bool: ...
|
||||
@overload
|
||||
def __getitem__(self, i: int) -> _T: ...
|
||||
@overload
|
||||
def __getitem__(self: _QS, s: slice) -> _QS: ...
|
||||
def __reversed__(self) -> Iterator[_T]: ...
|
||||
|
||||
_Row = TypeVar("_Row", covariant=True)
|
||||
|
||||
|
||||
@@ -205,10 +205,9 @@ class CallableChoiceIterator:
|
||||
def __iter__(self) -> None: ...
|
||||
|
||||
class ChoiceField(Field):
|
||||
choices: Any = ...
|
||||
def __init__(
|
||||
self,
|
||||
choices: Any = ...,
|
||||
choices: Union[_FieldChoices, Callable[[], _FieldChoices]] = ...,
|
||||
required: bool = ...,
|
||||
widget: Optional[Union[Widget, Type[Widget]]] = ...,
|
||||
label: Optional[Any] = ...,
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
from datetime import datetime
|
||||
from typing import Any, Callable, Dict, Iterator, List, Mapping, MutableMapping, Optional, Sequence, Tuple, Type, Union
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterator,
|
||||
List,
|
||||
Mapping,
|
||||
MutableMapping,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
ClassVar,
|
||||
Container,
|
||||
TypeVar,
|
||||
)
|
||||
from unittest.mock import MagicMock
|
||||
from uuid import UUID
|
||||
|
||||
@@ -24,6 +40,11 @@ _Fields = Union[List[Union[Callable, str]], Sequence[str], Literal["__all__"]]
|
||||
_Labels = Dict[str, str]
|
||||
_ErrorMessages = Dict[str, Dict[str, str]]
|
||||
|
||||
_M = TypeVar("_M", bound=Model)
|
||||
|
||||
def construct_instance(
|
||||
form: BaseForm, instance: _M, fields: Optional[Container[str]] = ..., exclude: Optional[Container[str]] = ...
|
||||
) -> _M: ...
|
||||
def model_to_dict(
|
||||
instance: Model, fields: Optional[_Fields] = ..., exclude: Optional[_Fields] = ...
|
||||
) -> Dict[str, Any]: ...
|
||||
@@ -76,7 +97,8 @@ class BaseModelForm(BaseForm):
|
||||
save_m2m: Any = ...
|
||||
def save(self, commit: bool = ...) -> Any: ...
|
||||
|
||||
class ModelForm(BaseModelForm): ...
|
||||
class ModelForm(BaseModelForm, metaclass=ModelFormMetaclass):
|
||||
base_fields: ClassVar[Dict[str, Field]] = ...
|
||||
|
||||
def modelform_factory(
|
||||
model: Type[Model],
|
||||
|
||||
@@ -18,6 +18,7 @@ from typing import (
|
||||
|
||||
from django.contrib.auth.base_user import AbstractBaseUser
|
||||
from django.contrib.sessions.backends.base import SessionBase
|
||||
from django.contrib.sites.models import Site
|
||||
from django.utils.datastructures import CaseInsensitiveMapping, ImmutableList, MultiValueDict
|
||||
|
||||
from django.core.files import uploadedfile, uploadhandler
|
||||
@@ -51,6 +52,7 @@ class HttpRequest(BytesIO):
|
||||
content_type: Optional[str] = ...
|
||||
content_params: Optional[Dict[str, str]] = ...
|
||||
user: AbstractBaseUser
|
||||
site: Site
|
||||
session: SessionBase
|
||||
encoding: Optional[str] = ...
|
||||
upload_handlers: UploadHandlerList = ...
|
||||
|
||||
@@ -64,6 +64,7 @@ class HttpResponseBase(Iterable[Any]):
|
||||
class HttpResponse(HttpResponseBase):
|
||||
client: Client
|
||||
context: Context
|
||||
content: Any
|
||||
csrf_cookie_set: bool
|
||||
redirect_chain: List[Tuple[str, int]]
|
||||
request: Dict[str, Any]
|
||||
@@ -78,10 +79,6 @@ class HttpResponse(HttpResponseBase):
|
||||
def __init__(self, content: object = ..., *args: Any, **kwargs: Any) -> None: ...
|
||||
def serialize(self) -> bytes: ...
|
||||
@property
|
||||
def content(self) -> Any: ...
|
||||
@content.setter
|
||||
def content(self, value: Any) -> None: ...
|
||||
@property
|
||||
def url(self) -> str: ...
|
||||
def json(self) -> Dict[str, Any]: ...
|
||||
|
||||
@@ -107,7 +104,7 @@ class FileResponse(StreamingHttpResponse):
|
||||
def json(self) -> Dict[str, Any]: ...
|
||||
|
||||
class HttpResponseRedirectBase(HttpResponse):
|
||||
allowed_schemes = ... # type: List[str]
|
||||
allowed_schemes: List[str] = ...
|
||||
def __init__(self, redirect_to: str, *args: Any, **kwargs: Any) -> None: ...
|
||||
|
||||
class HttpResponseRedirect(HttpResponseRedirectBase): ...
|
||||
|
||||
@@ -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, ClassVar
|
||||
from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple, Type, Union, ClassVar, overload
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.handlers.wsgi import WSGIHandler
|
||||
@@ -146,9 +146,14 @@ class TransactionTestCase(SimpleTestCase):
|
||||
ordered: bool = ...,
|
||||
msg: Optional[str] = ...,
|
||||
) -> None: ...
|
||||
@overload
|
||||
def assertNumQueries(
|
||||
self, num: int, func: Optional[Union[Callable, Type[list]]] = ..., *args: Any, using: Any = ..., **kwargs: Any
|
||||
) -> Optional[_AssertNumQueriesContext]: ...
|
||||
self, num: int, func: Callable[..., Any], *args: Any, using: str = ..., **kwargs: Any
|
||||
) -> None: ...
|
||||
@overload
|
||||
def assertNumQueries(
|
||||
self, num: int, func: None = ..., *args: Any, using: str = ..., **kwargs: Any
|
||||
) -> _AssertNumQueriesContext: ...
|
||||
|
||||
class TestCase(TransactionTestCase):
|
||||
@classmethod
|
||||
|
||||
@@ -1,25 +1,59 @@
|
||||
from typing import Any, Optional
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any, TypeVar, overload, Union
|
||||
|
||||
from django.utils.functional import Promise
|
||||
from typing_extensions import Literal
|
||||
|
||||
class DjangoUnicodeDecodeError(UnicodeDecodeError):
|
||||
obj: bytes = ...
|
||||
def __init__(self, obj: bytes, *args: Any) -> None: ...
|
||||
|
||||
python_2_unicode_compatible: Any
|
||||
|
||||
_P = TypeVar("_P", bound=Promise)
|
||||
_S = TypeVar("_S", bound=str)
|
||||
_PT = TypeVar("_PT", None, int, float, Decimal, datetime.datetime, datetime.date, datetime.time)
|
||||
@overload
|
||||
def smart_text(s: _P, encoding: str = ..., strings_only: bool = ..., errors: str = ...) -> _P: ...
|
||||
@overload
|
||||
def smart_text(s: _PT, encoding: str = ..., strings_only: Literal[True] = ..., errors: str = ...) -> _PT: ...
|
||||
@overload
|
||||
def smart_text(s: _S, encoding: str = ..., strings_only: bool = ..., errors: str = ...) -> _S: ...
|
||||
@overload
|
||||
def smart_text(s: Any, encoding: str = ..., strings_only: bool = ..., errors: str = ...) -> str: ...
|
||||
def is_protected_type(obj: Any) -> bool: ...
|
||||
def force_text(s: Any, encoding: str = ..., strings_only: bool = ..., errors: str = ...) -> Optional[str]: ...
|
||||
@overload
|
||||
def force_text(s: _PT, encoding: str = ..., strings_only: Literal[True] = ..., errors: str = ...) -> _PT: ...
|
||||
@overload
|
||||
def force_text(s: _S, encoding: str = ..., strings_only: bool = ..., errors: str = ...) -> _S: ...
|
||||
@overload
|
||||
def force_text(s: Any, encoding: str = ..., strings_only: bool = ..., errors: str = ...) -> str: ...
|
||||
@overload
|
||||
def smart_bytes(s: _P, encoding: str = ..., strings_only: bool = ..., errors: str = ...) -> _P: ...
|
||||
@overload
|
||||
def smart_bytes(s: _PT, encoding: str = ..., strings_only: Literal[True] = ..., errors: str = ...) -> _PT: ...
|
||||
@overload
|
||||
def smart_bytes(s: Any, encoding: str = ..., strings_only: bool = ..., errors: str = ...) -> bytes: ...
|
||||
@overload
|
||||
def force_bytes(s: _PT, encoding: str = ..., strings_only: Literal[True] = ..., errors: str = ...) -> _PT: ...
|
||||
@overload
|
||||
def force_bytes(s: Any, encoding: str = ..., strings_only: bool = ..., errors: str = ...) -> bytes: ...
|
||||
|
||||
smart_str = smart_text
|
||||
force_str = force_text
|
||||
|
||||
def iri_to_uri(iri: Optional[str]) -> Optional[str]: ...
|
||||
def uri_to_iri(uri: Optional[str]) -> Optional[str]: ...
|
||||
@overload
|
||||
def iri_to_uri(iri: None) -> None: ...
|
||||
@overload
|
||||
def iri_to_uri(iri: Union[str, Promise]) -> str: ...
|
||||
@overload
|
||||
def uri_to_iri(iri: None) -> None: ...
|
||||
@overload
|
||||
def uri_to_iri(iri: str) -> str: ...
|
||||
def escape_uri_path(path: str) -> str: ...
|
||||
def repercent_broken_unicode(path: bytes) -> bytes: ...
|
||||
def filepath_to_uri(path: Optional[str]) -> Optional[str]: ...
|
||||
@overload
|
||||
def filepath_to_uri(path: None) -> None: ...
|
||||
@overload
|
||||
def filepath_to_uri(path: str) -> str: ...
|
||||
def get_system_encoding() -> str: ...
|
||||
|
||||
DEFAULT_LOCALE_ENCODING: Any
|
||||
|
||||
@@ -10,7 +10,7 @@ class Node:
|
||||
connector: str = ...
|
||||
negated: bool = ...
|
||||
def __init__(
|
||||
self, children: Optional[_NodeChildren] = ..., 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: ...
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from typing import Any, Callable, Dict, Optional, Sequence, Type, Union
|
||||
|
||||
from django.forms.forms import BaseForm
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
|
||||
from django.views.generic.detail import BaseDetailView, SingleObjectMixin, SingleObjectTemplateResponseMixin
|
||||
from typing_extensions import Literal
|
||||
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
|
||||
class FormMixin(ContextMixin):
|
||||
initial: Dict[str, Any] = ...
|
||||
form_class: Optional[Type[BaseForm]] = ...
|
||||
|
||||
113
mfile.py
Normal file
113
mfile.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from graphviz import Digraph
|
||||
from mypy.options import Options
|
||||
|
||||
source = """
|
||||
from root.package import MyQuerySet
|
||||
|
||||
MyQuerySet().mymethod()
|
||||
"""
|
||||
|
||||
from mypy import parse
|
||||
|
||||
parsed = parse.parse(source, 'myfile.py', None, None, Options())
|
||||
print(parsed)
|
||||
|
||||
graphattrs = {
|
||||
"labelloc": "t",
|
||||
"fontcolor": "blue",
|
||||
# "bgcolor": "#333333",
|
||||
"margin": "0",
|
||||
}
|
||||
|
||||
nodeattrs = {
|
||||
# "color": "white",
|
||||
"fontcolor": "#00008b",
|
||||
# "style": "filled",
|
||||
# "fillcolor": "#ffffff",
|
||||
# "fillcolor": "#006699",
|
||||
}
|
||||
|
||||
edgeattrs = {
|
||||
# "color": "white",
|
||||
# "fontcolor": "white",
|
||||
}
|
||||
|
||||
graph = Digraph('mfile.py', graph_attr=graphattrs, node_attr=nodeattrs, edge_attr=edgeattrs)
|
||||
graph.node('__builtins__')
|
||||
|
||||
graph.node('django.db.models')
|
||||
graph.node('django.db.models.fields')
|
||||
|
||||
graph.edge('django.db.models', 'django.db.models.fields')
|
||||
graph.edge('django.db.models', '__builtins__')
|
||||
graph.edge('django.db.models.fields', '__builtins__')
|
||||
|
||||
graph.node('mymodule')
|
||||
graph.edge('mymodule', 'django.db.models')
|
||||
graph.edge('mymodule', '__builtins__')
|
||||
#
|
||||
# graph.node('ImportFrom', label='ImportFrom(val=root.package, [MyQuerySet])')
|
||||
# graph.edge('MypyFile', 'ImportFrom')
|
||||
|
||||
|
||||
|
||||
# graph.node('ClassDef_MyQuerySet', label='ClassDef(name=MyQuerySet)')
|
||||
# graph.edge('MypyFile', 'ClassDef_MyQuerySet')
|
||||
#
|
||||
# graph.node('FuncDef_mymethod', label='FuncDef(name=mymethod)')
|
||||
# graph.edge('ClassDef_MyQuerySet', 'FuncDef_mymethod')
|
||||
#
|
||||
# graph.node('Args', label='Args')
|
||||
# graph.edge('FuncDef_mymethod', 'Args')
|
||||
#
|
||||
# graph.node('Var_self', label='Var(name=self)')
|
||||
# graph.edge('Args', 'Var_self')
|
||||
#
|
||||
# graph.node('Block', label='Block')
|
||||
# graph.edge('FuncDef_mymethod', 'Block')
|
||||
#
|
||||
# graph.node('PassStmt')
|
||||
# graph.edge('Block', 'PassStmt')
|
||||
|
||||
# graph.node('ExpressionStmt')
|
||||
# graph.edge('MypyFile', 'ExpressionStmt')
|
||||
#
|
||||
# graph.node('CallExpr', label='CallExpr(val="MyQuerySet()")')
|
||||
# graph.edge('ExpressionStmt', 'CallExpr')
|
||||
#
|
||||
# graph.node('MemberExpr', label='MemberExpr(val=".mymethod()")')
|
||||
# graph.edge('CallExpr', 'MemberExpr')
|
||||
#
|
||||
# graph.node('CallExpr_outer_Args', label='Args()')
|
||||
# graph.edge('CallExpr', 'CallExpr_outer_Args')
|
||||
#
|
||||
# graph.node('CallExpr_inner', label='CallExpr(val="mymethod()")')
|
||||
# graph.edge('MemberExpr', 'CallExpr_inner')
|
||||
#
|
||||
# graph.node('NameExpr', label='NameExpr(val="mymethod")')
|
||||
# graph.edge('CallExpr_inner', 'NameExpr')
|
||||
#
|
||||
# graph.node('Expression_Args', label='Args()')
|
||||
# graph.edge('CallExpr_inner', 'Expression_Args')
|
||||
|
||||
graph.render(view=True, format='png')
|
||||
|
||||
|
||||
# MypyFile(
|
||||
# ClassDef(
|
||||
# name=MyQuerySet,
|
||||
# FuncDef(
|
||||
# name=mymethod,
|
||||
# Args(
|
||||
# Var(self))
|
||||
# Block(PassStmt())
|
||||
# )
|
||||
# )
|
||||
# ExpressionStmt:6(
|
||||
# CallExpr:6(
|
||||
# MemberExpr:6(
|
||||
# CallExpr:6(
|
||||
# NameExpr(MyQuerySet)
|
||||
# Args())
|
||||
# mymethod)
|
||||
# Args())))
|
||||
13
mfile.py.gv
Normal file
13
mfile.py.gv
Normal file
@@ -0,0 +1,13 @@
|
||||
digraph "mfile.py" {
|
||||
graph [fontcolor=blue labelloc=t margin=0]
|
||||
node [fontcolor="#00008b"]
|
||||
__builtins__
|
||||
"django.db.models"
|
||||
"django.db.models.fields"
|
||||
"django.db.models" -> "django.db.models.fields"
|
||||
"django.db.models" -> __builtins__
|
||||
"django.db.models.fields" -> __builtins__
|
||||
mymodule
|
||||
mymodule -> "django.db.models"
|
||||
mymodule -> __builtins__
|
||||
}
|
||||
BIN
mfile.py.gv.pdf
Normal file
BIN
mfile.py.gv.pdf
Normal file
Binary file not shown.
BIN
mfile.py.gv.png
Normal file
BIN
mfile.py.gv.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
9
my.gv
Normal file
9
my.gv
Normal file
@@ -0,0 +1,9 @@
|
||||
digraph AST {
|
||||
File
|
||||
ClassDef
|
||||
ClassDef -> File
|
||||
FuncDef
|
||||
FuncDef -> ClassDef
|
||||
ExpressionStmt
|
||||
ExpressionStmt -> File
|
||||
}
|
||||
@@ -11,7 +11,7 @@ from django.db import models
|
||||
from django.db.models.base import Model
|
||||
from django.db.models.fields import AutoField, CharField, Field
|
||||
from django.db.models.fields.related import ForeignKey, RelatedField
|
||||
from django.db.models.fields.reverse_related import ForeignObjectRel
|
||||
from django.db.models.fields.reverse_related import ForeignObjectRel, ManyToOneRel, ManyToManyRel, OneToOneRel
|
||||
from django.db.models.lookups import Exact
|
||||
from django.db.models.sql.query import Query
|
||||
from django.utils.functional import cached_property
|
||||
@@ -21,7 +21,7 @@ from mypy.types import AnyType, Instance
|
||||
from mypy.types import Type as MypyType
|
||||
from mypy.types import TypeOfAny, UnionType
|
||||
|
||||
from mypy_django_plugin.lib import fullnames, helpers
|
||||
from mypy_django_plugin.lib import chk_helpers, fullnames, helpers
|
||||
|
||||
try:
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
@@ -55,10 +55,10 @@ def initialize_django(settings_module: str) -> Tuple['Apps', 'LazySettings']:
|
||||
def noop_class_getitem(cls, key):
|
||||
return cls
|
||||
|
||||
from django.db import models
|
||||
# from django.db import models
|
||||
|
||||
models.QuerySet.__class_getitem__ = classmethod(noop_class_getitem) # type: ignore
|
||||
models.Manager.__class_getitem__ = classmethod(noop_class_getitem) # type: ignore
|
||||
# models.QuerySet.__class_getitem__ = classmethod(noop_class_getitem) # type: ignore
|
||||
# models.Manager.__class_getitem__ = classmethod(noop_class_getitem) # type: ignore
|
||||
|
||||
from django.conf import settings
|
||||
from django.apps import apps
|
||||
@@ -119,12 +119,12 @@ class DjangoContext:
|
||||
if isinstance(field, Field):
|
||||
yield field
|
||||
|
||||
def get_model_relations(self, model_cls: Type[Model]) -> Iterator[ForeignObjectRel]:
|
||||
for field in model_cls._meta.get_fields():
|
||||
if isinstance(field, ForeignObjectRel):
|
||||
yield field
|
||||
def get_model_relations(self, model_cls: Type[Model]) -> Iterator[Tuple[Optional[str], ForeignObjectRel]]:
|
||||
for relation in model_cls._meta.get_fields():
|
||||
if isinstance(relation, ForeignObjectRel):
|
||||
yield relation.get_accessor_name(), relation
|
||||
|
||||
def get_field_lookup_exact_type(self, api: TypeChecker, field: Field) -> MypyType:
|
||||
def get_field_lookup_exact_type(self, api: TypeChecker, field: Union[Field, ForeignObjectRel]) -> MypyType:
|
||||
if isinstance(field, (RelatedField, ForeignObjectRel)):
|
||||
related_model_cls = field.related_model
|
||||
primary_key_field = self.get_primary_key_field(related_model_cls)
|
||||
@@ -134,10 +134,8 @@ class DjangoContext:
|
||||
if rel_model_info is None:
|
||||
return AnyType(TypeOfAny.explicit)
|
||||
|
||||
model_and_primary_key_type = UnionType.make_union([Instance(rel_model_info, []),
|
||||
primary_key_type])
|
||||
model_and_primary_key_type = UnionType.make_union([Instance(rel_model_info, []), primary_key_type])
|
||||
return helpers.make_optional(model_and_primary_key_type)
|
||||
# return helpers.make_optional(Instance(rel_model_info, []))
|
||||
|
||||
field_info = helpers.lookup_class_typeinfo(api, field.__class__)
|
||||
if field_info is None:
|
||||
@@ -228,21 +226,22 @@ class DjangoContext:
|
||||
attname = field.attname
|
||||
return attname
|
||||
|
||||
def get_field_nullability(self, field: Field, method: Optional[str]) -> bool:
|
||||
def get_field_nullability(self, field: Union[Field, ForeignObjectRel], method: Optional[str] = None) -> bool:
|
||||
nullable = field.null
|
||||
if not nullable and isinstance(field, CharField) and field.blank:
|
||||
return True
|
||||
if method == '__init__':
|
||||
if field.primary_key or isinstance(field, ForeignKey):
|
||||
if ((isinstance(field, Field) and field.primary_key)
|
||||
or isinstance(field, ForeignKey)):
|
||||
return True
|
||||
if method == 'create':
|
||||
if isinstance(field, AutoField):
|
||||
return True
|
||||
if field.has_default():
|
||||
if isinstance(field, Field) and field.has_default():
|
||||
return True
|
||||
return nullable
|
||||
|
||||
def get_field_set_type(self, api: TypeChecker, field: Field, *, method: str) -> MypyType:
|
||||
def get_field_set_type(self, api: TypeChecker, field: Union[Field, ForeignObjectRel], *, method: str) -> MypyType:
|
||||
""" Get a type of __set__ for this specific Django field. """
|
||||
target_field = field
|
||||
if isinstance(field, ForeignKey):
|
||||
@@ -259,7 +258,7 @@ class DjangoContext:
|
||||
field_set_type = helpers.convert_any_to_type(field_set_type, argument_field_type)
|
||||
return field_set_type
|
||||
|
||||
def get_field_get_type(self, api: TypeChecker, field: Field, *, method: str) -> MypyType:
|
||||
def get_field_get_type(self, api: TypeChecker, field: Union[Field, ForeignObjectRel], *, method: str) -> MypyType:
|
||||
""" Get a type of __get__ for this specific Django field. """
|
||||
field_info = helpers.lookup_class_typeinfo(api, field.__class__)
|
||||
if field_info is None:
|
||||
@@ -303,7 +302,10 @@ class DjangoContext:
|
||||
|
||||
return related_model_cls
|
||||
|
||||
def _resolve_field_from_parts(self, field_parts: Iterable[str], model_cls: Type[Model]) -> Field:
|
||||
def _resolve_field_from_parts(self,
|
||||
field_parts: Iterable[str],
|
||||
model_cls: Type[Model]
|
||||
) -> Union[Field, ForeignObjectRel]:
|
||||
currently_observed_model = model_cls
|
||||
field = None
|
||||
for field_part in field_parts:
|
||||
@@ -325,7 +327,7 @@ class DjangoContext:
|
||||
assert field is not None
|
||||
return field
|
||||
|
||||
def resolve_lookup_into_field(self, model_cls: Type[Model], lookup: str) -> Field:
|
||||
def resolve_lookup_into_field(self, model_cls: Type[Model], lookup: str) -> Union[Field, ForeignObjectRel]:
|
||||
query = Query(model_cls)
|
||||
lookup_parts, field_parts, is_expression = query.solve_lookup_type(lookup)
|
||||
if lookup_parts:
|
||||
@@ -354,11 +356,11 @@ class DjangoContext:
|
||||
return AnyType(TypeOfAny.explicit)
|
||||
|
||||
if lookup_cls is None or isinstance(lookup_cls, Exact):
|
||||
return self.get_field_lookup_exact_type(helpers.get_typechecker_api(ctx), field)
|
||||
return self.get_field_lookup_exact_type(chk_helpers.get_typechecker_api(ctx), field)
|
||||
|
||||
assert lookup_cls is not None
|
||||
|
||||
lookup_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), lookup_cls)
|
||||
lookup_info = helpers.lookup_class_typeinfo(chk_helpers.get_typechecker_api(ctx), lookup_cls)
|
||||
if lookup_info is None:
|
||||
return AnyType(TypeOfAny.explicit)
|
||||
|
||||
@@ -368,7 +370,7 @@ class DjangoContext:
|
||||
# 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):
|
||||
field_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), field.__class__)
|
||||
field_info = helpers.lookup_class_typeinfo(chk_helpers.get_typechecker_api(ctx), field.__class__)
|
||||
if field_info is None:
|
||||
return AnyType(TypeOfAny.explicit)
|
||||
lookup_type = helpers.get_private_descriptor_type(field_info, '_pyi_private_get_type',
|
||||
|
||||
120
mypy_django_plugin/lib/chk_helpers.py
Normal file
120
mypy_django_plugin/lib/chk_helpers.py
Normal file
@@ -0,0 +1,120 @@
|
||||
from typing import Dict, List, Optional, Set, Union
|
||||
|
||||
from mypy import checker
|
||||
from mypy.checker import TypeChecker
|
||||
from mypy.nodes import (
|
||||
GDEF, MDEF, Expression, MypyFile, SymbolTableNode, TypeInfo, Var,
|
||||
)
|
||||
from mypy.plugin import (
|
||||
AttributeContext, CheckerPluginInterface, FunctionContext, MethodContext,
|
||||
)
|
||||
from mypy.types import AnyType, Instance, TupleType
|
||||
from mypy.types import Type as MypyType
|
||||
from mypy.types import TypedDictType, TypeOfAny
|
||||
|
||||
from mypy_django_plugin.lib import helpers
|
||||
|
||||
|
||||
def add_new_class_for_current_module(current_module: MypyFile,
|
||||
name: str,
|
||||
bases: List[Instance],
|
||||
fields: Optional[Dict[str, MypyType]] = None
|
||||
) -> TypeInfo:
|
||||
new_class_unique_name = checker.gen_unique_name(name, current_module.names)
|
||||
new_typeinfo = helpers.new_typeinfo(new_class_unique_name,
|
||||
bases=bases,
|
||||
module_name=current_module.fullname)
|
||||
# new_typeinfo = helpers.make_new_typeinfo_in_current_module(new_class_unique_name,
|
||||
# bases=bases,
|
||||
# current_module_fullname=current_module.fullname)
|
||||
# add fields
|
||||
if fields:
|
||||
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)
|
||||
|
||||
current_module.names[new_class_unique_name] = SymbolTableNode(GDEF, new_typeinfo, plugin_generated=True)
|
||||
current_module.defs.append(new_typeinfo.defn)
|
||||
return new_typeinfo
|
||||
|
||||
|
||||
def make_oneoff_named_tuple(api: TypeChecker, name: str, fields: 'Dict[str, MypyType]') -> TupleType:
|
||||
current_module = helpers.get_current_module(api)
|
||||
namedtuple_info = add_new_class_for_current_module(current_module, name,
|
||||
bases=[api.named_generic_type('typing.NamedTuple', [])],
|
||||
fields=fields)
|
||||
return TupleType(list(fields.values()), fallback=Instance(namedtuple_info, []))
|
||||
|
||||
|
||||
def make_tuple(api: 'TypeChecker', fields: List[MypyType]) -> TupleType:
|
||||
# fallback for tuples is any builtins.tuple instance
|
||||
fallback = api.named_generic_type('builtins.tuple',
|
||||
[AnyType(TypeOfAny.special_form)])
|
||||
return TupleType(fields, fallback=fallback)
|
||||
|
||||
|
||||
def make_oneoff_typeddict(api: CheckerPluginInterface, fields: 'Dict[str, MypyType]',
|
||||
required_keys: Set[str]) -> TypedDictType:
|
||||
object_type = api.named_generic_type('mypy_extensions._TypedDict', [])
|
||||
typed_dict_type = TypedDictType(fields, # type: ignore
|
||||
required_keys=required_keys,
|
||||
fallback=object_type)
|
||||
return typed_dict_type
|
||||
|
||||
|
||||
def get_typechecker_api(ctx: Union[AttributeContext, MethodContext, FunctionContext]) -> TypeChecker:
|
||||
if not isinstance(ctx.api, TypeChecker):
|
||||
raise ValueError('Not a TypeChecker')
|
||||
return ctx.api
|
||||
|
||||
|
||||
def check_types_compatible(ctx: Union[FunctionContext, MethodContext],
|
||||
*, expected_type: MypyType, actual_type: MypyType, error_message: str) -> None:
|
||||
api = get_typechecker_api(ctx)
|
||||
api.check_subtype(actual_type, expected_type,
|
||||
ctx.context, error_message,
|
||||
'got', 'expected')
|
||||
|
||||
|
||||
def get_call_argument_by_name(ctx: Union[FunctionContext, MethodContext], name: str) -> Optional[Expression]:
|
||||
"""
|
||||
Return the expression for the specific argument.
|
||||
This helper should only be used with non-star arguments.
|
||||
"""
|
||||
if name not in ctx.callee_arg_names:
|
||||
return None
|
||||
idx = ctx.callee_arg_names.index(name)
|
||||
args = ctx.args[idx]
|
||||
if len(args) != 1:
|
||||
# Either an error or no value passed.
|
||||
return None
|
||||
return args[0]
|
||||
|
||||
|
||||
def get_call_argument_type_by_name(ctx: Union[FunctionContext, MethodContext], name: str) -> Optional[MypyType]:
|
||||
"""Return the type for the specific argument.
|
||||
|
||||
This helper should only be used with non-star arguments.
|
||||
"""
|
||||
if name not in ctx.callee_arg_names:
|
||||
return None
|
||||
idx = ctx.callee_arg_names.index(name)
|
||||
arg_types = ctx.arg_types[idx]
|
||||
if len(arg_types) != 1:
|
||||
# Either an error or no value passed.
|
||||
return None
|
||||
return arg_types[0]
|
||||
|
||||
|
||||
def add_new_sym_for_info(info: TypeInfo, *, name: str, sym_type: MypyType) -> None:
|
||||
# type=: type of the variable itself
|
||||
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.is_initialized_in_class = True
|
||||
var.is_inferred = True
|
||||
info.names[name] = SymbolTableNode(MDEF, var,
|
||||
plugin_generated=True)
|
||||
@@ -13,7 +13,7 @@ DUMMY_SETTINGS_BASE_CLASS = 'django.conf._DjangoConfLazyObject'
|
||||
QUERYSET_CLASS_FULLNAME = 'django.db.models.query.QuerySet'
|
||||
BASE_MANAGER_CLASS_FULLNAME = 'django.db.models.manager.BaseManager'
|
||||
MANAGER_CLASS_FULLNAME = 'django.db.models.manager.Manager'
|
||||
RELATED_MANAGER_CLASS_FULLNAME = 'django.db.models.manager.RelatedManager'
|
||||
RELATED_MANAGER_CLASS = 'django.db.models.manager.RelatedManager'
|
||||
|
||||
BASEFORM_CLASS_FULLNAME = 'django.forms.forms.BaseForm'
|
||||
FORM_CLASS_FULLNAME = 'django.forms.forms.Form'
|
||||
@@ -23,9 +23,7 @@ FORM_MIXIN_CLASS_FULLNAME = 'django.views.generic.edit.FormMixin'
|
||||
|
||||
MANAGER_CLASSES = {
|
||||
MANAGER_CLASS_FULLNAME,
|
||||
RELATED_MANAGER_CLASS_FULLNAME,
|
||||
BASE_MANAGER_CLASS_FULLNAME,
|
||||
# QUERYSET_CLASS_FULLNAME
|
||||
}
|
||||
|
||||
RELATED_FIELDS_CLASSES = {
|
||||
|
||||
6
mypy_django_plugin/lib/generics.py
Normal file
6
mypy_django_plugin/lib/generics.py
Normal file
@@ -0,0 +1,6 @@
|
||||
def make_classes_generic(*klasses: type) -> None:
|
||||
for klass in klasses:
|
||||
def fake_classgetitem(cls, *args, **kwargs):
|
||||
return cls
|
||||
|
||||
klass.__class_getitem__ = classmethod(fake_classgetitem) # type: ignore
|
||||
@@ -1,68 +1,372 @@
|
||||
from collections import OrderedDict
|
||||
from abc import abstractmethod
|
||||
from typing import (
|
||||
TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Optional, Set, Union, cast,
|
||||
)
|
||||
TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union,
|
||||
cast)
|
||||
|
||||
from django.db.models.fields import Field
|
||||
from django.db.models.fields.related import RelatedField
|
||||
from django.db.models.fields.reverse_related import ForeignObjectRel
|
||||
from mypy import checker
|
||||
from mypy.checker import TypeChecker
|
||||
from mypy.mro import calculate_mro
|
||||
from mypy.nodes import (
|
||||
GDEF, MDEF, Block, ClassDef, Expression, MemberExpr, MypyFile, NameExpr, StrExpr, SymbolNode, SymbolTable,
|
||||
SymbolTableNode, TypeInfo, Var,
|
||||
)
|
||||
from mypy.plugin import (
|
||||
AttributeContext, CheckerPluginInterface, FunctionContext, MethodContext,
|
||||
)
|
||||
from mypy.types import AnyType, Instance, NoneTyp, TupleType
|
||||
Block, ClassDef, Expression, MemberExpr, MypyFile, NameExpr, StrExpr, SymbolTable, SymbolTableNode,
|
||||
TypeInfo, Var,
|
||||
CallExpr, Context, PlaceholderNode, FuncDef, FakeInfo, OverloadedFuncDef, Decorator)
|
||||
from mypy.plugin import DynamicClassDefContext, ClassDefContext, AttributeContext, MethodContext
|
||||
from mypy.plugins.common import add_method
|
||||
from mypy.semanal import SemanticAnalyzer, is_valid_replacement, is_same_symbol
|
||||
from mypy.types import AnyType, Instance, NoneTyp, TypeType, ProperType, CallableType
|
||||
from mypy.types import Type as MypyType
|
||||
from mypy.types import TypedDictType, TypeOfAny, UnionType
|
||||
from mypy.types import TypeOfAny, UnionType
|
||||
from mypy.typetraverser import TypeTraverserVisitor
|
||||
|
||||
from django.db.models.fields import Field
|
||||
from mypy_django_plugin.lib import fullnames
|
||||
from mypy_django_plugin.lib.sem_helpers import prepare_unannotated_method_signature, analyze_callable_signature
|
||||
from mypy_django_plugin.transformers2 import new_helpers
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mypy_django_plugin.django.context import DjangoContext
|
||||
from mypy_django_plugin.main import NewSemanalDjangoPlugin
|
||||
|
||||
AnyPluginAPI = Union[TypeChecker, SemanticAnalyzer]
|
||||
|
||||
|
||||
class DjangoPluginCallback:
|
||||
django_context: 'DjangoContext'
|
||||
|
||||
def __init__(self, plugin: 'NewSemanalDjangoPlugin') -> None:
|
||||
self.plugin = plugin
|
||||
self.django_context = plugin.django_context
|
||||
|
||||
def new_typeinfo(self, name: str, bases: List[Instance]) -> TypeInfo:
|
||||
class_def = ClassDef(name, Block([]))
|
||||
class_def.fullname = self.qualified_name(name)
|
||||
|
||||
info = TypeInfo(SymbolTable(), class_def, self.get_current_module().fullname)
|
||||
info.bases = bases
|
||||
calculate_mro(info)
|
||||
info.metaclass_type = info.calculate_metaclass_type()
|
||||
|
||||
class_def.info = info
|
||||
return info
|
||||
|
||||
@abstractmethod
|
||||
def get_current_module(self) -> MypyFile:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def qualified_name(self, name: str) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class SemanalPluginCallback(DjangoPluginCallback):
|
||||
semanal_api: SemanticAnalyzer
|
||||
|
||||
def build_defer_error_message(self, message: str) -> str:
|
||||
return f'{self.__class__.__name__}: {message}'
|
||||
|
||||
def defer_till_next_iteration(self, deferral_context: Optional[Context] = None,
|
||||
*,
|
||||
reason: Optional[str] = None) -> bool:
|
||||
""" Returns False if cannot be deferred. """
|
||||
if self.semanal_api.final_iteration:
|
||||
return False
|
||||
self.semanal_api.defer(deferral_context)
|
||||
print(f'LOG: defer: {self.build_defer_error_message(reason)}')
|
||||
return True
|
||||
|
||||
def get_current_module(self) -> MypyFile:
|
||||
return self.semanal_api.cur_mod_node
|
||||
|
||||
def qualified_name(self, name: str) -> str:
|
||||
return self.semanal_api.qualified_name(name)
|
||||
|
||||
def lookup_typeinfo_or_defer(self, fullname: str, *,
|
||||
deferral_context: Optional[Context] = None,
|
||||
reason_for_defer: Optional[str] = None) -> Optional[TypeInfo]:
|
||||
sym = self.plugin.lookup_fully_qualified(fullname)
|
||||
if sym is None or sym.node is None or isinstance(sym.node, PlaceholderNode):
|
||||
deferral_context = deferral_context or self.semanal_api.cur_mod_node
|
||||
reason = reason_for_defer or f'{fullname!r} is not available for lookup'
|
||||
if not self.defer_till_next_iteration(deferral_context, reason=reason):
|
||||
raise new_helpers.TypeInfoNotFound(fullname)
|
||||
return None
|
||||
|
||||
if not isinstance(sym.node, TypeInfo):
|
||||
raise ValueError(f'{fullname!r} does not correspond to TypeInfo')
|
||||
|
||||
return sym.node
|
||||
|
||||
def new_typeinfo(self, name: str, bases: List[Instance], module_fullname: Optional[str] = None) -> TypeInfo:
|
||||
class_def = ClassDef(name, Block([]))
|
||||
class_def.fullname = self.semanal_api.qualified_name(name)
|
||||
|
||||
info = TypeInfo(SymbolTable(), class_def,
|
||||
module_fullname or self.get_current_module().fullname)
|
||||
info.bases = bases
|
||||
calculate_mro(info)
|
||||
info.metaclass_type = info.calculate_metaclass_type()
|
||||
|
||||
class_def.info = info
|
||||
return info
|
||||
|
||||
def add_symbol_table_node(self,
|
||||
name: str,
|
||||
symbol: SymbolTableNode,
|
||||
symbol_table: Optional[SymbolTable] = None,
|
||||
context: Optional[Context] = None,
|
||||
can_defer: bool = True,
|
||||
escape_comprehensions: bool = False) -> None:
|
||||
""" Patched copy of SemanticAnalyzer.add_symbol_table_node(). """
|
||||
names = symbol_table or self.semanal_api.current_symbol_table(escape_comprehensions=escape_comprehensions)
|
||||
existing = names.get(name)
|
||||
if isinstance(symbol.node, PlaceholderNode) and can_defer:
|
||||
self.semanal_api.defer(context)
|
||||
return None
|
||||
if (existing is not None
|
||||
and context is not None
|
||||
and not is_valid_replacement(existing, symbol)):
|
||||
# There is an existing node, so this may be a redefinition.
|
||||
# If the new node points to the same node as the old one,
|
||||
# or if both old and new nodes are placeholders, we don't
|
||||
# need to do anything.
|
||||
old = existing.node
|
||||
new = symbol.node
|
||||
if isinstance(new, PlaceholderNode):
|
||||
# We don't know whether this is okay. Let's wait until the next iteration.
|
||||
return False
|
||||
if not is_same_symbol(old, new):
|
||||
if isinstance(new, (FuncDef, Decorator, OverloadedFuncDef, TypeInfo)):
|
||||
self.semanal_api.add_redefinition(names, name, symbol)
|
||||
if not (isinstance(new, (FuncDef, Decorator))
|
||||
and self.semanal_api.set_original_def(old, new)):
|
||||
self.semanal_api.name_already_defined(name, context, existing)
|
||||
elif name not in self.semanal_api.missing_names and '*' not in self.semanal_api.missing_names:
|
||||
names[name] = symbol
|
||||
self.progress = True
|
||||
return None
|
||||
raise new_helpers.SymbolAdditionNotPossible()
|
||||
|
||||
# def add_symbol_table_node_or_defer(self, name: str, sym: SymbolTableNode) -> bool:
|
||||
# return self.semanal_api.add_symbol_table_node(name, sym,
|
||||
# context=self.semanal_api.cur_mod_node)
|
||||
|
||||
def add_method_from_signature(self,
|
||||
signature_node: FuncDef,
|
||||
new_method_name: str,
|
||||
new_self_type: Instance,
|
||||
class_defn: ClassDef) -> bool:
|
||||
if signature_node.type is None:
|
||||
if self.defer_till_next_iteration(reason=signature_node.fullname):
|
||||
return False
|
||||
|
||||
arguments, return_type = prepare_unannotated_method_signature(signature_node)
|
||||
ctx = ClassDefContext(class_defn, signature_node, self.semanal_api)
|
||||
add_method(ctx,
|
||||
new_method_name,
|
||||
self_type=new_self_type,
|
||||
args=arguments,
|
||||
return_type=return_type)
|
||||
return True
|
||||
|
||||
# add imported objects from method signature to the current module, if not present
|
||||
source_symbols = self.semanal_api.modules[signature_node.info.module_name].names
|
||||
currently_imported_symbols = self.semanal_api.cur_mod_node.names
|
||||
|
||||
def import_symbol_from_source(name: str) -> None:
|
||||
if name in source_symbols['__builtins__'].node.names:
|
||||
return
|
||||
sym = source_symbols[name].copy()
|
||||
self.semanal_api.add_imported_symbol(name, sym, context=self.semanal_api.cur_mod_node)
|
||||
|
||||
class UnimportedTypesVisitor(TypeTraverserVisitor):
|
||||
def visit_instance(self, t: Instance) -> None:
|
||||
super().visit_instance(t)
|
||||
if isinstance(t.type, FakeInfo):
|
||||
return
|
||||
type_name = t.type.name
|
||||
sym = currently_imported_symbols.get(type_name)
|
||||
if sym is None:
|
||||
import_symbol_from_source(type_name)
|
||||
|
||||
signature_node.type.accept(UnimportedTypesVisitor())
|
||||
|
||||
# # copy global SymbolTableNode objects from original class to the current node, if not present
|
||||
# original_module = semanal_api.modules[method_node.info.module_name]
|
||||
# for name, sym in original_module.names.items():
|
||||
# if (not sym.plugin_generated
|
||||
# and name not in semanal_api.cur_mod_node.names):
|
||||
# semanal_api.add_imported_symbol(name, sym, context=semanal_api.cur_mod_node)
|
||||
|
||||
arguments, analyzed_return_type, unbound = analyze_callable_signature(self.semanal_api, signature_node)
|
||||
if unbound:
|
||||
raise new_helpers.IncompleteDefnError(f'Signature of method {signature_node.fullname!r} is not ready')
|
||||
|
||||
assert len(arguments) + 1 == len(signature_node.arguments)
|
||||
assert analyzed_return_type is not None
|
||||
|
||||
ctx = ClassDefContext(class_defn, signature_node, self.semanal_api)
|
||||
add_method(ctx,
|
||||
new_method_name,
|
||||
self_type=new_self_type,
|
||||
args=arguments,
|
||||
return_type=analyzed_return_type)
|
||||
return True
|
||||
|
||||
|
||||
class DynamicClassPluginCallback(SemanalPluginCallback):
|
||||
class_name: str
|
||||
call_expr: CallExpr
|
||||
|
||||
def __call__(self, ctx: DynamicClassDefContext) -> None:
|
||||
self.class_name = ctx.name
|
||||
self.call_expr = ctx.call
|
||||
self.semanal_api = cast(SemanticAnalyzer, ctx.api)
|
||||
self.create_new_dynamic_class()
|
||||
|
||||
def get_callee(self) -> MemberExpr:
|
||||
callee = self.call_expr.callee
|
||||
assert isinstance(callee, MemberExpr)
|
||||
return callee
|
||||
|
||||
def lookup_same_module_or_defer(self, name: str, *,
|
||||
deferral_context: Optional[Context] = None) -> Optional[SymbolTableNode]:
|
||||
sym = self.semanal_api.lookup_qualified(name, self.call_expr)
|
||||
if sym is None or sym.node is None or isinstance(sym.node, PlaceholderNode):
|
||||
deferral_context = deferral_context or self.call_expr
|
||||
if not self.defer_till_next_iteration(deferral_context,
|
||||
reason=f'{self.semanal_api.cur_mod_id}.{name} does not exist'):
|
||||
raise new_helpers.NameNotFound(name)
|
||||
return None
|
||||
return sym
|
||||
|
||||
@abstractmethod
|
||||
def create_new_dynamic_class(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ClassDefPluginCallback(SemanalPluginCallback):
|
||||
reason: Expression
|
||||
class_defn: ClassDef
|
||||
ctx: ClassDefContext
|
||||
|
||||
def __call__(self, ctx: ClassDefContext) -> None:
|
||||
self.reason = ctx.reason
|
||||
self.class_defn = ctx.cls
|
||||
self.semanal_api = cast(SemanticAnalyzer, ctx.api)
|
||||
self.ctx = ctx
|
||||
self.modify_class_defn()
|
||||
|
||||
@abstractmethod
|
||||
def modify_class_defn(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class TypeCheckerPluginCallback(DjangoPluginCallback):
|
||||
type_checker: TypeChecker
|
||||
|
||||
def get_current_module(self) -> MypyFile:
|
||||
current_module = None
|
||||
for item in reversed(self.type_checker.scope.stack):
|
||||
if isinstance(item, MypyFile):
|
||||
current_module = item
|
||||
break
|
||||
assert current_module is not None
|
||||
return current_module
|
||||
|
||||
def qualified_name(self, name: str) -> str:
|
||||
return self.type_checker.scope.stack[-1].fullname + '.' + name
|
||||
|
||||
def lookup_typeinfo(self, fullname: str) -> Optional[TypeInfo]:
|
||||
sym = self.plugin.lookup_fully_qualified(fullname)
|
||||
if sym is None or sym.node is None:
|
||||
return None
|
||||
if not isinstance(sym.node, TypeInfo):
|
||||
raise ValueError(f'{fullname!r} does not correspond to TypeInfo')
|
||||
return sym.node
|
||||
|
||||
|
||||
class GetMethodPluginCallback(TypeCheckerPluginCallback):
|
||||
callee_type: Instance
|
||||
ctx: MethodContext
|
||||
|
||||
def __call__(self, ctx: MethodContext) -> MypyType:
|
||||
self.type_checker = ctx.api
|
||||
|
||||
assert isinstance(ctx.type, CallableType)
|
||||
self.callee_type = ctx.type.ret_type
|
||||
self.ctx = ctx
|
||||
return self.get_method_return_type()
|
||||
|
||||
@abstractmethod
|
||||
def get_method_return_type(self) -> MypyType:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class GetAttributeCallback(TypeCheckerPluginCallback):
|
||||
obj_type: ProperType
|
||||
default_attr_type: MypyType
|
||||
error_context: MemberExpr
|
||||
name: str
|
||||
|
||||
def __call__(self, ctx: AttributeContext) -> MypyType:
|
||||
self.ctx = ctx
|
||||
self.type_checker = ctx.api
|
||||
self.obj_type = ctx.type
|
||||
self.default_attr_type = ctx.default_attr_type
|
||||
self.error_context = ctx.context
|
||||
assert isinstance(self.error_context, MemberExpr)
|
||||
self.name = self.error_context.name
|
||||
return self.default_attr_type
|
||||
|
||||
|
||||
def get_django_metadata(model_info: TypeInfo) -> Dict[str, Any]:
|
||||
return model_info.metadata.setdefault('django', {})
|
||||
|
||||
|
||||
class IncompleteDefnException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def lookup_fully_qualified_sym(fullname: str, all_modules: Dict[str, MypyFile]) -> Optional[SymbolTableNode]:
|
||||
def split_symbol_name(fullname: str, all_modules: Dict[str, MypyFile]) -> Optional[Tuple[str, str]]:
|
||||
if '.' not in fullname:
|
||||
return None
|
||||
module, cls_name = fullname.rsplit('.', 1)
|
||||
|
||||
module_file = all_modules.get(module)
|
||||
if module_file is None:
|
||||
module_name = None
|
||||
parts = fullname.split('.')
|
||||
for i in range(len(parts), 0, -1):
|
||||
possible_module_name = '.'.join(parts[:i])
|
||||
if possible_module_name in all_modules:
|
||||
module_name = possible_module_name
|
||||
break
|
||||
if module_name is None:
|
||||
return None
|
||||
sym = module_file.names.get(cls_name)
|
||||
if sym is None:
|
||||
|
||||
symbol_name = fullname.replace(module_name, '').lstrip('.')
|
||||
return module_name, symbol_name
|
||||
|
||||
|
||||
def lookup_fully_qualified_typeinfo(api: AnyPluginAPI, fullname: str) -> Optional[TypeInfo]:
|
||||
split = split_symbol_name(fullname, api.modules)
|
||||
if split is None:
|
||||
return None
|
||||
return sym
|
||||
module_name, cls_name = split
|
||||
|
||||
sym_table = api.modules[module_name].names # type: Dict[str, SymbolTableNode]
|
||||
if '.' in cls_name:
|
||||
parent_cls_name, _, cls_name = cls_name.rpartition('.')
|
||||
# nested class
|
||||
for parent_cls_name in parent_cls_name.split('.'):
|
||||
sym = sym_table.get(parent_cls_name)
|
||||
if (sym is None or sym.node is None
|
||||
or not isinstance(sym.node, TypeInfo)):
|
||||
return None
|
||||
sym_table = sym.node.names
|
||||
|
||||
def lookup_fully_qualified_generic(name: str, all_modules: Dict[str, MypyFile]) -> Optional[SymbolNode]:
|
||||
sym = lookup_fully_qualified_sym(name, all_modules)
|
||||
if sym is None:
|
||||
sym = sym_table.get(cls_name)
|
||||
if (sym is None
|
||||
or sym.node is None
|
||||
or not isinstance(sym.node, TypeInfo)):
|
||||
return None
|
||||
return sym.node
|
||||
|
||||
|
||||
def lookup_fully_qualified_typeinfo(api: TypeChecker, fullname: str) -> Optional[TypeInfo]:
|
||||
node = lookup_fully_qualified_generic(fullname, api.modules)
|
||||
if not isinstance(node, TypeInfo):
|
||||
return None
|
||||
return node
|
||||
|
||||
|
||||
def lookup_class_typeinfo(api: TypeChecker, klass: type) -> Optional[TypeInfo]:
|
||||
def lookup_class_typeinfo(api: AnyPluginAPI, klass: type) -> Optional[TypeInfo]:
|
||||
fullname = get_class_fullname(klass)
|
||||
field_info = lookup_fully_qualified_typeinfo(api, fullname)
|
||||
return field_info
|
||||
@@ -77,36 +381,6 @@ def get_class_fullname(klass: type) -> str:
|
||||
return klass.__module__ + '.' + klass.__qualname__
|
||||
|
||||
|
||||
def get_call_argument_by_name(ctx: Union[FunctionContext, MethodContext], name: str) -> Optional[Expression]:
|
||||
"""
|
||||
Return the expression for the specific argument.
|
||||
This helper should only be used with non-star arguments.
|
||||
"""
|
||||
if name not in ctx.callee_arg_names:
|
||||
return None
|
||||
idx = ctx.callee_arg_names.index(name)
|
||||
args = ctx.args[idx]
|
||||
if len(args) != 1:
|
||||
# Either an error or no value passed.
|
||||
return None
|
||||
return args[0]
|
||||
|
||||
|
||||
def get_call_argument_type_by_name(ctx: Union[FunctionContext, MethodContext], name: str) -> Optional[MypyType]:
|
||||
"""Return the type for the specific argument.
|
||||
|
||||
This helper should only be used with non-star arguments.
|
||||
"""
|
||||
if name not in ctx.callee_arg_names:
|
||||
return None
|
||||
idx = ctx.callee_arg_names.index(name)
|
||||
arg_types = ctx.arg_types[idx]
|
||||
if len(arg_types) != 1:
|
||||
# Either an error or no value passed.
|
||||
return None
|
||||
return arg_types[0]
|
||||
|
||||
|
||||
def make_optional(typ: MypyType) -> MypyType:
|
||||
return UnionType.make_union([typ, NoneTyp()])
|
||||
|
||||
@@ -151,7 +425,7 @@ def get_private_descriptor_type(type_info: TypeInfo, private_field_name: str, is
|
||||
return AnyType(TypeOfAny.explicit)
|
||||
|
||||
|
||||
def get_field_lookup_exact_type(api: TypeChecker, field: Field) -> MypyType:
|
||||
def get_field_lookup_exact_type(api: AnyPluginAPI, field: Field) -> MypyType:
|
||||
if isinstance(field, (RelatedField, ForeignObjectRel)):
|
||||
lookup_type_class = field.related_model
|
||||
rel_model_info = lookup_class_typeinfo(api, lookup_type_class)
|
||||
@@ -166,40 +440,10 @@ def get_field_lookup_exact_type(api: TypeChecker, field: Field) -> MypyType:
|
||||
is_nullable=field.null)
|
||||
|
||||
|
||||
def get_nested_meta_node_for_current_class(info: TypeInfo) -> Optional[TypeInfo]:
|
||||
metaclass_sym = info.names.get('Meta')
|
||||
if metaclass_sym is not None and isinstance(metaclass_sym.node, TypeInfo):
|
||||
return metaclass_sym.node
|
||||
return None
|
||||
def get_current_module(api: AnyPluginAPI) -> MypyFile:
|
||||
if isinstance(api, SemanticAnalyzer):
|
||||
return api.cur_mod_node
|
||||
|
||||
|
||||
def add_new_class_for_module(module: MypyFile, name: str, bases: List[Instance],
|
||||
fields: 'OrderedDict[str, MypyType]') -> TypeInfo:
|
||||
new_class_unique_name = checker.gen_unique_name(name, module.names)
|
||||
|
||||
# make new class expression
|
||||
classdef = ClassDef(new_class_unique_name, Block([]))
|
||||
classdef.fullname = module.fullname + '.' + new_class_unique_name
|
||||
|
||||
# make new TypeInfo
|
||||
new_typeinfo = TypeInfo(SymbolTable(), classdef, module.fullname)
|
||||
new_typeinfo.bases = bases
|
||||
calculate_mro(new_typeinfo)
|
||||
new_typeinfo.calculate_metaclass_type()
|
||||
|
||||
# add fields
|
||||
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)
|
||||
return new_typeinfo
|
||||
|
||||
|
||||
def get_current_module(api: TypeChecker) -> MypyFile:
|
||||
current_module = None
|
||||
for item in reversed(api.scope.stack):
|
||||
if isinstance(item, MypyFile):
|
||||
@@ -209,21 +453,6 @@ def get_current_module(api: TypeChecker) -> MypyFile:
|
||||
return current_module
|
||||
|
||||
|
||||
def make_oneoff_named_tuple(api: TypeChecker, name: str, fields: 'OrderedDict[str, MypyType]') -> TupleType:
|
||||
current_module = get_current_module(api)
|
||||
namedtuple_info = add_new_class_for_module(current_module, name,
|
||||
bases=[api.named_generic_type('typing.NamedTuple', [])],
|
||||
fields=fields)
|
||||
return TupleType(list(fields.values()), fallback=Instance(namedtuple_info, []))
|
||||
|
||||
|
||||
def make_tuple(api: 'TypeChecker', fields: List[MypyType]) -> TupleType:
|
||||
# fallback for tuples is any builtins.tuple instance
|
||||
fallback = api.named_generic_type('builtins.tuple',
|
||||
[AnyType(TypeOfAny.special_form)])
|
||||
return TupleType(fields, fallback=fallback)
|
||||
|
||||
|
||||
def convert_any_to_type(typ: MypyType, referred_to_type: MypyType) -> MypyType:
|
||||
if isinstance(typ, UnionType):
|
||||
converted_items = []
|
||||
@@ -246,15 +475,7 @@ def convert_any_to_type(typ: MypyType, referred_to_type: MypyType) -> MypyType:
|
||||
return typ
|
||||
|
||||
|
||||
def make_typeddict(api: CheckerPluginInterface, fields: 'OrderedDict[str, MypyType]',
|
||||
required_keys: Set[str]) -> TypedDictType:
|
||||
object_type = api.named_generic_type('mypy_extensions._TypedDict', [])
|
||||
typed_dict_type = TypedDictType(fields, required_keys=required_keys, fallback=object_type)
|
||||
return typed_dict_type
|
||||
|
||||
|
||||
def resolve_string_attribute_value(attr_expr: Expression, ctx: Union[FunctionContext, MethodContext],
|
||||
django_context: 'DjangoContext') -> Optional[str]:
|
||||
def resolve_string_attribute_value(attr_expr: Expression, django_context: 'DjangoContext') -> Optional[str]:
|
||||
if isinstance(attr_expr, StrExpr):
|
||||
return attr_expr.value
|
||||
|
||||
@@ -264,37 +485,28 @@ def resolve_string_attribute_value(attr_expr: Expression, ctx: Union[FunctionCon
|
||||
if isinstance(attr_expr.expr, NameExpr) and attr_expr.expr.fullname == 'django.conf.settings':
|
||||
if hasattr(django_context.settings, member_name):
|
||||
return getattr(django_context.settings, member_name)
|
||||
|
||||
ctx.api.fail(f'Expression of type {type(attr_expr).__name__!r} is not supported', ctx.context)
|
||||
return None
|
||||
|
||||
|
||||
def get_typechecker_api(ctx: Union[AttributeContext, MethodContext, FunctionContext]) -> TypeChecker:
|
||||
if not isinstance(ctx.api, TypeChecker):
|
||||
raise ValueError('Not a TypeChecker')
|
||||
return cast(TypeChecker, ctx.api)
|
||||
|
||||
|
||||
def is_model_subclass_info(info: TypeInfo, django_context: 'DjangoContext') -> bool:
|
||||
def is_subclass_of_model(info: TypeInfo, django_context: 'DjangoContext') -> bool:
|
||||
return (info.fullname in django_context.all_registered_model_class_fullnames
|
||||
or info.has_base(fullnames.MODEL_CLASS_FULLNAME))
|
||||
|
||||
|
||||
def check_types_compatible(ctx: Union[FunctionContext, MethodContext],
|
||||
*, expected_type: MypyType, actual_type: MypyType, error_message: str) -> None:
|
||||
api = get_typechecker_api(ctx)
|
||||
api.check_subtype(actual_type, expected_type,
|
||||
ctx.context, error_message,
|
||||
'got', 'expected')
|
||||
def new_typeinfo(name: str,
|
||||
*,
|
||||
bases: List[Instance],
|
||||
module_name: str) -> TypeInfo:
|
||||
"""
|
||||
Construct new TypeInfo instance. Cannot be used for nested classes.
|
||||
"""
|
||||
class_def = ClassDef(name, Block([]))
|
||||
class_def.fullname = module_name + '.' + name
|
||||
|
||||
info = TypeInfo(SymbolTable(), class_def, module_name)
|
||||
info.bases = bases
|
||||
calculate_mro(info)
|
||||
info.metaclass_type = info.calculate_metaclass_type()
|
||||
|
||||
def add_new_sym_for_info(info: TypeInfo, *, name: str, sym_type: MypyType) -> None:
|
||||
# type=: type of the variable itself
|
||||
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.is_initialized_in_class = True
|
||||
var.is_inferred = True
|
||||
info.names[name] = SymbolTableNode(MDEF, var,
|
||||
plugin_generated=True)
|
||||
class_def.info = info
|
||||
return info
|
||||
|
||||
120
mypy_django_plugin/lib/sem_helpers.py
Normal file
120
mypy_django_plugin/lib/sem_helpers.py
Normal file
@@ -0,0 +1,120 @@
|
||||
from typing import List, NamedTuple, Optional, Tuple, Union, cast
|
||||
|
||||
from mypy.nodes import Argument, FuncDef, TypeInfo, Var
|
||||
from mypy.plugin import ClassDefContext, DynamicClassDefContext
|
||||
from mypy.plugins.common import add_method
|
||||
from mypy.semanal import SemanticAnalyzer
|
||||
from mypy.types import AnyType, CallableType, Instance, PlaceholderType
|
||||
from mypy.types import Type as MypyType
|
||||
from mypy.types import TypeOfAny, get_proper_type
|
||||
|
||||
|
||||
class IncompleteDefnError(Exception):
|
||||
def __init__(self, error_message: str = '') -> None:
|
||||
super().__init__(error_message)
|
||||
|
||||
|
||||
class BoundNameNotFound(IncompleteDefnError):
|
||||
def __init__(self, fullname: str) -> None:
|
||||
super().__init__(f'No {fullname!r} found')
|
||||
|
||||
|
||||
def get_semanal_api(ctx: Union[ClassDefContext, DynamicClassDefContext]) -> SemanticAnalyzer:
|
||||
return cast(SemanticAnalyzer, ctx.api)
|
||||
|
||||
|
||||
def get_nested_meta_node_for_current_class(info: TypeInfo) -> Optional[TypeInfo]:
|
||||
metaclass_sym = info.names.get('Meta')
|
||||
if metaclass_sym is not None and isinstance(metaclass_sym.node, TypeInfo):
|
||||
return metaclass_sym.node
|
||||
return None
|
||||
|
||||
|
||||
def prepare_unannotated_method_signature(method_node: FuncDef) -> Tuple[List[Argument], MypyType]:
|
||||
prepared_arguments = []
|
||||
for argument in method_node.arguments[1:]:
|
||||
argument.type_annotation = AnyType(TypeOfAny.unannotated)
|
||||
prepared_arguments.append(argument)
|
||||
return_type = AnyType(TypeOfAny.unannotated)
|
||||
return prepared_arguments, return_type
|
||||
|
||||
|
||||
class SignatureTuple(NamedTuple):
|
||||
arguments: List[Argument]
|
||||
return_type: Optional[MypyType]
|
||||
cannot_be_bound: bool
|
||||
|
||||
|
||||
def analyze_callable_signature(api: SemanticAnalyzer, method_node: FuncDef) -> SignatureTuple:
|
||||
method_type = method_node.type
|
||||
assert isinstance(method_type, CallableType)
|
||||
|
||||
arguments = []
|
||||
unbound = False
|
||||
for arg_name, arg_type, original_argument in zip(method_type.arg_names[1:],
|
||||
method_type.arg_types[1:],
|
||||
method_node.arguments[1:]):
|
||||
analyzed_arg_type = api.anal_type(get_proper_type(arg_type), allow_placeholder=True)
|
||||
assert analyzed_arg_type is not None
|
||||
if isinstance(analyzed_arg_type, PlaceholderType):
|
||||
unbound = True
|
||||
|
||||
var = Var(name=original_argument.variable.name,
|
||||
type=analyzed_arg_type)
|
||||
var.set_line(original_argument.variable)
|
||||
|
||||
argument = Argument(variable=var,
|
||||
type_annotation=analyzed_arg_type,
|
||||
initializer=original_argument.initializer,
|
||||
kind=original_argument.kind)
|
||||
argument.set_line(original_argument)
|
||||
arguments.append(argument)
|
||||
|
||||
analyzed_ret_type = api.anal_type(get_proper_type(method_type.ret_type), allow_placeholder=True)
|
||||
assert analyzed_ret_type is not None
|
||||
if isinstance(analyzed_ret_type, PlaceholderType):
|
||||
unbound = True
|
||||
return SignatureTuple(arguments, analyzed_ret_type, unbound)
|
||||
|
||||
|
||||
def copy_method_or_incomplete_defn_exception(ctx: ClassDefContext,
|
||||
self_type: Instance,
|
||||
new_method_name: str,
|
||||
method_node: FuncDef) -> None:
|
||||
semanal_api = get_semanal_api(ctx)
|
||||
|
||||
if method_node.type is None:
|
||||
if not semanal_api.final_iteration:
|
||||
raise IncompleteDefnError(f'Unannotated method {method_node.fullname!r}')
|
||||
|
||||
arguments, return_type = prepare_unannotated_method_signature(method_node)
|
||||
add_method(ctx,
|
||||
new_method_name,
|
||||
args=arguments,
|
||||
return_type=return_type,
|
||||
self_type=self_type)
|
||||
return
|
||||
|
||||
assert isinstance(method_node.type, CallableType)
|
||||
|
||||
# copy global SymbolTableNode objects from original class to the current node, if not present
|
||||
original_module = semanal_api.modules[method_node.info.module_name]
|
||||
for name, sym in original_module.names.items():
|
||||
if (not sym.plugin_generated
|
||||
and name not in semanal_api.cur_mod_node.names):
|
||||
semanal_api.add_imported_symbol(name, sym, context=semanal_api.cur_mod_node)
|
||||
|
||||
arguments, analyzed_return_type, unbound = analyze_callable_signature(semanal_api, method_node)
|
||||
assert len(arguments) + 1 == len(method_node.arguments)
|
||||
if unbound:
|
||||
raise IncompleteDefnError(f'Signature of method {method_node.fullname!r} is not ready')
|
||||
|
||||
assert analyzed_return_type is not None
|
||||
|
||||
if new_method_name in ctx.cls.info.names:
|
||||
del ctx.cls.info.names[new_method_name]
|
||||
add_method(ctx,
|
||||
new_method_name,
|
||||
args=arguments,
|
||||
return_type=analyzed_return_type,
|
||||
self_type=self_type)
|
||||
@@ -7,7 +7,7 @@ from mypy.errors import Errors
|
||||
from mypy.nodes import MypyFile, TypeInfo
|
||||
from mypy.options import Options
|
||||
from mypy.plugin import (
|
||||
AttributeContext, ClassDefContext, FunctionContext, MethodContext, Plugin,
|
||||
AttributeContext, ClassDefContext, DynamicClassDefContext, FunctionContext, MethodContext, Plugin,
|
||||
)
|
||||
from mypy.types import Type as MypyType
|
||||
|
||||
@@ -18,6 +18,9 @@ from mypy_django_plugin.transformers import (
|
||||
fields, forms, init_create, meta, querysets, request, settings,
|
||||
)
|
||||
from mypy_django_plugin.transformers.models import process_model_class
|
||||
from mypy_django_plugin.transformers2.dynamic_managers import CreateNewManagerClassFrom_FromQuerySet
|
||||
from mypy_django_plugin.transformers2.models import ModelCallback
|
||||
from mypy_django_plugin.transformers2.related_managers import GetRelatedManagerCallback
|
||||
|
||||
|
||||
def transform_model_class(ctx: ClassDefContext,
|
||||
@@ -120,6 +123,10 @@ class NewSemanalDjangoPlugin(Plugin):
|
||||
return 10, module, -1
|
||||
|
||||
def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
|
||||
# load QuerySet and Manager together (for as_manager)
|
||||
if file.fullname == 'django.db.models.query':
|
||||
return [self._new_dependency('django.db.models.manager')]
|
||||
|
||||
# for settings
|
||||
if file.fullname == 'django.conf' and self.django_context.django_settings_module:
|
||||
return [self._new_dependency(self.django_context.django_settings_module)]
|
||||
@@ -168,16 +175,12 @@ class NewSemanalDjangoPlugin(Plugin):
|
||||
if fullname == 'django.contrib.auth.get_user_model':
|
||||
return partial(settings.get_user_model_hook, django_context=self.django_context)
|
||||
|
||||
manager_bases = self._get_current_manager_bases()
|
||||
if fullname in manager_bases:
|
||||
return querysets.determine_proper_manager_type
|
||||
|
||||
info = self._get_typeinfo_or_none(fullname)
|
||||
if info:
|
||||
if info.has_base(fullnames.FIELD_FULLNAME):
|
||||
return partial(fields.transform_into_proper_return_type, django_context=self.django_context)
|
||||
|
||||
if helpers.is_model_subclass_info(info, self.django_context):
|
||||
if helpers.is_subclass_of_model(info, self.django_context):
|
||||
return partial(init_create.redefine_and_typecheck_model_init, django_context=self.django_context)
|
||||
return None
|
||||
|
||||
@@ -221,7 +224,7 @@ class NewSemanalDjangoPlugin(Plugin):
|
||||
) -> Optional[Callable[[ClassDefContext], None]]:
|
||||
if (fullname in self.django_context.all_registered_model_class_fullnames
|
||||
or fullname in self._get_current_model_bases()):
|
||||
return partial(transform_model_class, django_context=self.django_context)
|
||||
return ModelCallback(self)
|
||||
|
||||
if fullname in self._get_current_manager_bases():
|
||||
return add_new_manager_base
|
||||
@@ -240,6 +243,20 @@ class NewSemanalDjangoPlugin(Plugin):
|
||||
info = self._get_typeinfo_or_none(class_name)
|
||||
if info and info.has_base(fullnames.HTTPREQUEST_CLASS_FULLNAME) and attr_name == 'user':
|
||||
return partial(request.set_auth_user_model_as_type_for_request_user, django_context=self.django_context)
|
||||
|
||||
if info and info.has_base(fullnames.MODEL_CLASS_FULLNAME):
|
||||
return GetRelatedManagerCallback(self)
|
||||
|
||||
return None
|
||||
|
||||
def get_dynamic_class_hook(self, fullname: str
|
||||
) -> Optional[Callable[[DynamicClassDefContext], None]]:
|
||||
if fullname.endswith('from_queryset'):
|
||||
class_name, _, _ = fullname.rpartition('.')
|
||||
info = self._get_typeinfo_or_none(class_name)
|
||||
if info and info.has_base(fullnames.BASE_MANAGER_CLASS_FULLNAME):
|
||||
return CreateNewManagerClassFrom_FromQuerySet(self)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
|
||||
0
mypy_django_plugin/py.typed
Normal file
0
mypy_django_plugin/py.typed
Normal file
@@ -9,13 +9,13 @@ from mypy.types import Type as MypyType
|
||||
from mypy.types import TypeOfAny
|
||||
|
||||
from mypy_django_plugin.django.context import DjangoContext
|
||||
from mypy_django_plugin.lib import fullnames, helpers
|
||||
from mypy_django_plugin.lib import chk_helpers, fullnames, helpers
|
||||
|
||||
|
||||
def _get_current_field_from_assignment(ctx: FunctionContext, django_context: DjangoContext) -> Optional[Field]:
|
||||
outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
|
||||
outer_model_info = chk_helpers.get_typechecker_api(ctx).scope.active_class()
|
||||
if (outer_model_info is None
|
||||
or not helpers.is_model_subclass_info(outer_model_info, django_context)):
|
||||
or not helpers.is_subclass_of_model(outer_model_info, django_context)):
|
||||
return None
|
||||
|
||||
field_name = None
|
||||
@@ -66,21 +66,21 @@ def fill_descriptor_types_for_related_field(ctx: FunctionContext, django_context
|
||||
# __get__/__set__ of ForeignKey of derived model
|
||||
for model_cls in django_context.all_registered_model_classes:
|
||||
if issubclass(model_cls, current_model_cls) and not model_cls._meta.abstract:
|
||||
derived_model_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), model_cls)
|
||||
derived_model_info = helpers.lookup_class_typeinfo(chk_helpers.get_typechecker_api(ctx), model_cls)
|
||||
if derived_model_info is not None:
|
||||
fk_ref_type = Instance(derived_model_info, [])
|
||||
derived_fk_type = reparametrize_related_field_type(default_related_field_type,
|
||||
set_type=fk_ref_type, get_type=fk_ref_type)
|
||||
helpers.add_new_sym_for_info(derived_model_info,
|
||||
name=current_field.name,
|
||||
sym_type=derived_fk_type)
|
||||
chk_helpers.add_new_sym_for_info(derived_model_info,
|
||||
name=current_field.name,
|
||||
sym_type=derived_fk_type)
|
||||
|
||||
related_model = related_model_cls
|
||||
related_model_to_set = related_model_cls
|
||||
if related_model_to_set._meta.proxy_for_model is not None:
|
||||
related_model_to_set = related_model_to_set._meta.proxy_for_model
|
||||
|
||||
typechecker_api = helpers.get_typechecker_api(ctx)
|
||||
typechecker_api = chk_helpers.get_typechecker_api(ctx)
|
||||
|
||||
related_model_info = helpers.lookup_class_typeinfo(typechecker_api, related_model)
|
||||
if related_model_info is None:
|
||||
@@ -110,11 +110,16 @@ def get_field_descriptor_types(field_info: TypeInfo, is_nullable: bool) -> Tuple
|
||||
return set_type, get_type
|
||||
|
||||
|
||||
def get_field_type(field_info: TypeInfo, is_nullable: bool) -> Instance:
|
||||
set_type, get_type = get_field_descriptor_types(field_info, is_nullable)
|
||||
return Instance(field_info, [set_type, get_type])
|
||||
|
||||
|
||||
def set_descriptor_types_for_field(ctx: FunctionContext) -> Instance:
|
||||
default_return_type = cast(Instance, ctx.default_return_type)
|
||||
|
||||
is_nullable = False
|
||||
null_expr = helpers.get_call_argument_by_name(ctx, 'null')
|
||||
null_expr = chk_helpers.get_call_argument_by_name(ctx, 'null')
|
||||
if null_expr is not None:
|
||||
is_nullable = helpers.parse_bool(null_expr) or False
|
||||
|
||||
@@ -122,10 +127,10 @@ def set_descriptor_types_for_field(ctx: FunctionContext) -> Instance:
|
||||
return helpers.reparametrize_instance(default_return_type, [set_type, get_type])
|
||||
|
||||
|
||||
def determine_type_of_array_field(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
|
||||
def determine_type_of_array_field(ctx: FunctionContext) -> MypyType:
|
||||
default_return_type = set_descriptor_types_for_field(ctx)
|
||||
|
||||
base_field_arg_type = helpers.get_call_argument_type_by_name(ctx, 'base_field')
|
||||
base_field_arg_type = chk_helpers.get_call_argument_type_by_name(ctx, 'base_field')
|
||||
if not base_field_arg_type or not isinstance(base_field_arg_type, Instance):
|
||||
return default_return_type
|
||||
|
||||
@@ -141,9 +146,9 @@ def transform_into_proper_return_type(ctx: FunctionContext, django_context: Djan
|
||||
default_return_type = ctx.default_return_type
|
||||
assert isinstance(default_return_type, Instance)
|
||||
|
||||
outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
|
||||
outer_model_info = chk_helpers.get_typechecker_api(ctx).scope.active_class()
|
||||
if (outer_model_info is None
|
||||
or not helpers.is_model_subclass_info(outer_model_info, django_context)):
|
||||
or not helpers.is_subclass_of_model(outer_model_info, django_context)):
|
||||
return ctx.default_return_type
|
||||
|
||||
assert isinstance(outer_model_info, TypeInfo)
|
||||
@@ -152,6 +157,6 @@ def transform_into_proper_return_type(ctx: FunctionContext, django_context: Djan
|
||||
return fill_descriptor_types_for_related_field(ctx, django_context)
|
||||
|
||||
if default_return_type.type.has_base(fullnames.ARRAY_FIELD_FULLNAME):
|
||||
return determine_type_of_array_field(ctx, django_context)
|
||||
return determine_type_of_array_field(ctx)
|
||||
|
||||
return set_descriptor_types_for_field(ctx)
|
||||
|
||||
@@ -5,11 +5,11 @@ from mypy.types import CallableType, Instance, NoneTyp
|
||||
from mypy.types import Type as MypyType
|
||||
from mypy.types import TypeType
|
||||
|
||||
from mypy_django_plugin.lib import helpers
|
||||
from mypy_django_plugin.lib import chk_helpers, sem_helpers
|
||||
|
||||
|
||||
def make_meta_nested_class_inherit_from_any(ctx: ClassDefContext) -> None:
|
||||
meta_node = helpers.get_nested_meta_node_for_current_class(ctx.cls.info)
|
||||
meta_node = sem_helpers.get_nested_meta_node_for_current_class(ctx.cls.info)
|
||||
if meta_node is None:
|
||||
if not ctx.api.final_iteration:
|
||||
ctx.api.defer()
|
||||
@@ -28,7 +28,7 @@ def extract_proper_type_for_get_form(ctx: MethodContext) -> MypyType:
|
||||
object_type = ctx.type
|
||||
assert isinstance(object_type, Instance)
|
||||
|
||||
form_class_type = helpers.get_call_argument_type_by_name(ctx, 'form_class')
|
||||
form_class_type = chk_helpers.get_call_argument_type_by_name(ctx, 'form_class')
|
||||
if form_class_type is None or isinstance(form_class_type, NoneTyp):
|
||||
form_class_type = get_specified_form_class(object_type)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from mypy.types import Instance
|
||||
from mypy.types import Type as MypyType
|
||||
|
||||
from mypy_django_plugin.django.context import DjangoContext
|
||||
from mypy_django_plugin.lib import helpers
|
||||
from mypy_django_plugin.lib import chk_helpers
|
||||
|
||||
|
||||
def get_actual_types(ctx: Union[MethodContext, FunctionContext],
|
||||
@@ -32,7 +32,7 @@ def get_actual_types(ctx: Union[MethodContext, FunctionContext],
|
||||
|
||||
def typecheck_model_method(ctx: Union[FunctionContext, MethodContext], django_context: DjangoContext,
|
||||
model_cls: Type[Model], method: str) -> MypyType:
|
||||
typechecker_api = helpers.get_typechecker_api(ctx)
|
||||
typechecker_api = chk_helpers.get_typechecker_api(ctx)
|
||||
expected_types = django_context.get_expected_types(typechecker_api, model_cls, method=method)
|
||||
expected_keys = [key for key in expected_types.keys() if key != 'pk']
|
||||
|
||||
@@ -42,11 +42,11 @@ def typecheck_model_method(ctx: Union[FunctionContext, MethodContext], django_co
|
||||
model_cls.__name__),
|
||||
ctx.context)
|
||||
continue
|
||||
helpers.check_types_compatible(ctx,
|
||||
expected_type=expected_types[actual_name],
|
||||
actual_type=actual_type,
|
||||
error_message='Incompatible type for "{}" of "{}"'.format(actual_name,
|
||||
model_cls.__name__))
|
||||
error_message = 'Incompatible type for "{}" of "{}"'.format(actual_name, model_cls.__name__)
|
||||
chk_helpers.check_types_compatible(ctx,
|
||||
expected_type=expected_types[actual_name],
|
||||
actual_type=actual_type,
|
||||
error_message=error_message)
|
||||
|
||||
return ctx.default_return_type
|
||||
|
||||
|
||||
232
mypy_django_plugin/transformers/managers.py
Normal file
232
mypy_django_plugin/transformers/managers.py
Normal file
@@ -0,0 +1,232 @@
|
||||
from typing import Any, Dict, Iterator, Optional, Tuple
|
||||
|
||||
from mypy.checker import gen_unique_name
|
||||
from mypy.nodes import (
|
||||
GDEF, CallExpr, Context, Decorator, FuncDef, MemberExpr, NameExpr, OverloadedFuncDef, PlaceholderNode, RefExpr,
|
||||
StrExpr, SymbolTable, SymbolTableNode, TypeInfo,
|
||||
MypyFile)
|
||||
from mypy.plugin import ClassDefContext, DynamicClassDefContext, MethodContext
|
||||
from mypy.semanal import SemanticAnalyzer, is_same_symbol, is_valid_replacement
|
||||
from mypy.types import AnyType, CallableType, Instance, TypeVarType, TypeVarDef
|
||||
from mypy.types import Type as MypyType
|
||||
from mypy.types import TypeOfAny
|
||||
|
||||
from mypy_django_plugin.lib import chk_helpers, fullnames, helpers, sem_helpers
|
||||
|
||||
|
||||
def iter_all_custom_queryset_methods(derived_queryset_info: TypeInfo) -> Iterator[Tuple[str, FuncDef]]:
|
||||
for base_queryset_info in derived_queryset_info.mro:
|
||||
if base_queryset_info.fullname == fullnames.QUERYSET_CLASS_FULLNAME:
|
||||
break
|
||||
for name, sym in base_queryset_info.names.items():
|
||||
if isinstance(sym.node, FuncDef):
|
||||
yield name, sym.node
|
||||
|
||||
|
||||
def generate_from_queryset_name(base_manager_info: TypeInfo, queryset_info: TypeInfo) -> str:
|
||||
return base_manager_info.name + 'From' + queryset_info.name
|
||||
|
||||
|
||||
#
|
||||
# def cb_resolve_callee_info_or_exception(cb: ) -> TypeInfo:
|
||||
# callee = ctx.call.callee
|
||||
# assert isinstance(callee, MemberExpr)
|
||||
# assert isinstance(callee.expr, RefExpr)
|
||||
#
|
||||
# callee_info = callee.expr.node
|
||||
# if (callee_info is None
|
||||
# or isinstance(callee_info, PlaceholderNode)):
|
||||
# raise sem_helpers.IncompleteDefnError(f'Definition of base manager {callee.fullname!r} '
|
||||
# f'is incomplete.')
|
||||
#
|
||||
# assert isinstance(callee_info, TypeInfo)
|
||||
# return callee_info
|
||||
|
||||
|
||||
def resolve_callee_info_or_exception(callback: helpers.DynamicClassPluginCallback) -> TypeInfo:
|
||||
callee = callback.call_expr.callee
|
||||
assert isinstance(callee, MemberExpr)
|
||||
assert isinstance(callee.expr, RefExpr)
|
||||
|
||||
callee_info = callee.expr.node
|
||||
if (callee_info is None
|
||||
or isinstance(callee_info, PlaceholderNode)):
|
||||
raise sem_helpers.IncompleteDefnError(f'Definition of base manager {callee.fullname!r} '
|
||||
f'is incomplete.')
|
||||
|
||||
assert isinstance(callee_info, TypeInfo)
|
||||
return callee_info
|
||||
|
||||
|
||||
def resolve_passed_queryset_info_or_exception(callback: helpers.DynamicClassPluginCallback) -> TypeInfo:
|
||||
passed_queryset_name_expr = callback.call_expr.args[0]
|
||||
assert isinstance(passed_queryset_name_expr, NameExpr)
|
||||
|
||||
# lookup in the same module
|
||||
sym = callback.semanal_api.lookup_qualified(passed_queryset_name_expr.name, ctx=callback.call_expr)
|
||||
if (sym is None
|
||||
or sym.node is None
|
||||
or isinstance(sym.node, PlaceholderNode)):
|
||||
bound_name = passed_queryset_name_expr.fullname or passed_queryset_name_expr.name
|
||||
raise sem_helpers.BoundNameNotFound(bound_name)
|
||||
|
||||
assert isinstance(sym.node, TypeInfo)
|
||||
return sym.node
|
||||
|
||||
|
||||
def resolve_django_manager_info_or_exception(callback: helpers.DynamicClassPluginCallback) -> TypeInfo:
|
||||
info = callback.lookup_typeinfo_or_defer(fullnames.MANAGER_CLASS_FULLNAME)
|
||||
if info is None:
|
||||
raise sem_helpers.BoundNameNotFound(fullnames.MANAGER_CLASS_FULLNAME)
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def new_manager_typeinfo(ctx: DynamicClassDefContext, callee_manager_info: TypeInfo) -> TypeInfo:
|
||||
callee_manager_type = Instance(callee_manager_info, [AnyType(TypeOfAny.unannotated)])
|
||||
api = sem_helpers.get_semanal_api(ctx)
|
||||
|
||||
new_manager_class_name = ctx.name
|
||||
new_manager_info = helpers.new_typeinfo(new_manager_class_name,
|
||||
bases=[callee_manager_type], module_name=api.cur_mod_id)
|
||||
new_manager_info.set_line(ctx.call)
|
||||
return new_manager_info
|
||||
|
||||
|
||||
def get_generated_manager_fullname(call: CallExpr, base_manager_info: TypeInfo, queryset_info: TypeInfo) -> str:
|
||||
if len(call.args) > 1:
|
||||
# only for from_queryset()
|
||||
expr = call.args[1]
|
||||
assert isinstance(expr, StrExpr)
|
||||
custom_manager_generated_name = expr.value
|
||||
else:
|
||||
custom_manager_generated_name = base_manager_info.name + 'From' + queryset_info.name
|
||||
|
||||
custom_manager_generated_fullname = 'django.db.models.manager' + '.' + custom_manager_generated_name
|
||||
return custom_manager_generated_fullname
|
||||
|
||||
|
||||
def get_generated_managers_metadata(django_manager_info: TypeInfo) -> Dict[str, Any]:
|
||||
return django_manager_info.metadata.setdefault('from_queryset_managers', {})
|
||||
|
||||
|
||||
def record_new_manager_info_fullname_into_metadata(ctx: DynamicClassDefContext,
|
||||
new_manager_fullname: str,
|
||||
callee_manager_info: TypeInfo,
|
||||
queryset_info: TypeInfo,
|
||||
django_manager_info: TypeInfo) -> None:
|
||||
custom_manager_generated_fullname = get_generated_manager_fullname(ctx.call,
|
||||
base_manager_info=callee_manager_info,
|
||||
queryset_info=queryset_info)
|
||||
metadata = get_generated_managers_metadata(django_manager_info)
|
||||
metadata[custom_manager_generated_fullname] = new_manager_fullname
|
||||
|
||||
|
||||
def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefContext) -> None:
|
||||
semanal_api = sem_helpers.get_semanal_api(ctx)
|
||||
try:
|
||||
callee_manager_info = resolve_callee_info_or_exception(ctx)
|
||||
queryset_info = resolve_passed_queryset_info_or_exception(ctx)
|
||||
django_manager_info = resolve_django_manager_info_or_exception(ctx)
|
||||
except sem_helpers.IncompleteDefnError:
|
||||
if not semanal_api.final_iteration:
|
||||
semanal_api.defer()
|
||||
return
|
||||
else:
|
||||
raise
|
||||
|
||||
new_manager_info = new_manager_typeinfo(ctx, callee_manager_info)
|
||||
record_new_manager_info_fullname_into_metadata(ctx,
|
||||
new_manager_info.fullname,
|
||||
callee_manager_info,
|
||||
queryset_info,
|
||||
django_manager_info)
|
||||
|
||||
class_def_context = ClassDefContext(cls=new_manager_info.defn,
|
||||
reason=ctx.call, api=semanal_api)
|
||||
self_type = Instance(new_manager_info, [AnyType(TypeOfAny.explicit)])
|
||||
|
||||
try:
|
||||
for name, method_node in iter_all_custom_queryset_methods(queryset_info):
|
||||
sem_helpers.copy_method_or_incomplete_defn_exception(class_def_context,
|
||||
self_type,
|
||||
new_method_name=name,
|
||||
method_node=method_node)
|
||||
except sem_helpers.IncompleteDefnError:
|
||||
if not semanal_api.final_iteration:
|
||||
semanal_api.defer()
|
||||
return
|
||||
else:
|
||||
raise
|
||||
|
||||
new_manager_sym = SymbolTableNode(GDEF, new_manager_info, plugin_generated=True)
|
||||
|
||||
# context=None - forcibly replace old node
|
||||
added = semanal_api.add_symbol_table_node(ctx.name, new_manager_sym, context=None)
|
||||
if added:
|
||||
# replace all references to the old manager Var everywhere
|
||||
for _, module in semanal_api.modules.items():
|
||||
if module.fullname != semanal_api.cur_mod_id:
|
||||
for sym_name, sym in module.names.items():
|
||||
if sym.fullname == new_manager_info.fullname:
|
||||
module.names[sym_name] = new_manager_sym.copy()
|
||||
|
||||
# we need another iteration to process methods
|
||||
if (not added
|
||||
and not semanal_api.final_iteration):
|
||||
semanal_api.defer()
|
||||
|
||||
|
||||
def add_symbol_table_node(api: SemanticAnalyzer,
|
||||
name: str,
|
||||
symbol: SymbolTableNode,
|
||||
context: Optional[Context] = None,
|
||||
symbol_table: Optional[SymbolTable] = None,
|
||||
can_defer: bool = True,
|
||||
escape_comprehensions: bool = False) -> bool:
|
||||
"""Add symbol table node to the currently active symbol table.
|
||||
|
||||
Return True if we actually added the symbol, or False if we refused
|
||||
to do so (because something is not ready or it was a no-op).
|
||||
|
||||
Generate an error if there is an invalid redefinition.
|
||||
|
||||
If context is None, unconditionally add node, since we can't report
|
||||
an error. Note that this is used by plugins to forcibly replace nodes!
|
||||
|
||||
TODO: Prevent plugins from replacing nodes, as it could cause problems?
|
||||
|
||||
Args:
|
||||
name: short name of symbol
|
||||
symbol: Node to add
|
||||
can_defer: if True, defer current target if adding a placeholder
|
||||
context: error context (see above about None value)
|
||||
"""
|
||||
names = symbol_table or api.current_symbol_table(escape_comprehensions=escape_comprehensions)
|
||||
existing = names.get(name)
|
||||
if isinstance(symbol.node, PlaceholderNode) and can_defer:
|
||||
api.defer(context)
|
||||
if (existing is not None
|
||||
and context is not None
|
||||
and not is_valid_replacement(existing, symbol)):
|
||||
# There is an existing node, so this may be a redefinition.
|
||||
# If the new node points to the same node as the old one,
|
||||
# or if both old and new nodes are placeholders, we don't
|
||||
# need to do anything.
|
||||
old = existing.node
|
||||
new = symbol.node
|
||||
if isinstance(new, PlaceholderNode):
|
||||
# We don't know whether this is okay. Let's wait until the next iteration.
|
||||
return False
|
||||
if not is_same_symbol(old, new):
|
||||
if isinstance(new, (FuncDef, Decorator, OverloadedFuncDef, TypeInfo)):
|
||||
api.add_redefinition(names, name, symbol)
|
||||
if not (isinstance(new, (FuncDef, Decorator))
|
||||
and api.set_original_def(old, new)):
|
||||
api.name_already_defined(name, context, existing)
|
||||
elif name not in api.missing_names and '*' not in api.missing_names:
|
||||
names[name] = symbol
|
||||
api.progress = True
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -5,12 +5,12 @@ from mypy.types import Type as MypyType
|
||||
from mypy.types import TypeOfAny
|
||||
|
||||
from mypy_django_plugin.django.context import DjangoContext
|
||||
from mypy_django_plugin.lib import helpers
|
||||
from mypy_django_plugin.lib import chk_helpers, helpers
|
||||
|
||||
|
||||
def _get_field_instance(ctx: MethodContext, field_fullname: str) -> MypyType:
|
||||
field_info = helpers.lookup_fully_qualified_typeinfo(helpers.get_typechecker_api(ctx),
|
||||
field_fullname)
|
||||
api = chk_helpers.get_typechecker_api(ctx)
|
||||
field_info = helpers.lookup_fully_qualified_typeinfo(api, field_fullname)
|
||||
if field_info is None:
|
||||
return AnyType(TypeOfAny.unannotated)
|
||||
return Instance(field_info, [AnyType(TypeOfAny.explicit), AnyType(TypeOfAny.explicit)])
|
||||
@@ -32,11 +32,11 @@ def return_proper_field_type_from_get_field(ctx: MethodContext, django_context:
|
||||
if model_cls is None:
|
||||
return ctx.default_return_type
|
||||
|
||||
field_name_expr = helpers.get_call_argument_by_name(ctx, 'field_name')
|
||||
field_name_expr = chk_helpers.get_call_argument_by_name(ctx, 'field_name')
|
||||
if field_name_expr is None:
|
||||
return ctx.default_return_type
|
||||
|
||||
field_name = helpers.resolve_string_attribute_value(field_name_expr, ctx, django_context)
|
||||
field_name = helpers.resolve_string_attribute_value(field_name_expr, django_context)
|
||||
if field_name is None:
|
||||
return ctx.default_return_type
|
||||
|
||||
|
||||
@@ -1,58 +1,96 @@
|
||||
from collections import OrderedDict
|
||||
from typing import List, Tuple, Type
|
||||
from typing import List, Optional, Type, cast
|
||||
|
||||
from django.db.models.base import Model
|
||||
from django.db.models.fields import DateField, DateTimeField
|
||||
from django.db.models.fields.related import ForeignKey
|
||||
from django.db.models.fields.related import ForeignKey, OneToOneField
|
||||
from django.db.models.fields.reverse_related import (
|
||||
ManyToManyRel, ManyToOneRel, OneToOneRel,
|
||||
)
|
||||
from mypy.nodes import ARG_STAR2, Argument, Context, FuncDef, TypeInfo, Var
|
||||
from mypy.nodes import (
|
||||
ARG_STAR2, GDEF, MDEF, Argument, Context, SymbolTableNode, TypeInfo, Var,
|
||||
)
|
||||
from mypy.plugin import ClassDefContext
|
||||
from mypy.plugins import common
|
||||
from mypy.plugins.common import add_method
|
||||
from mypy.types import AnyType, CallableType, Instance
|
||||
from mypy.semanal import SemanticAnalyzer, dummy_context
|
||||
from mypy.types import AnyType, Instance
|
||||
from mypy.types import Type as MypyType
|
||||
from mypy.types import TypeOfAny
|
||||
|
||||
from mypy_django_plugin.django.context import DjangoContext
|
||||
from mypy_django_plugin.lib import fullnames, helpers
|
||||
from mypy_django_plugin.lib import fullnames, helpers, sem_helpers
|
||||
from mypy_django_plugin.transformers import fields
|
||||
from mypy_django_plugin.transformers.fields import get_field_descriptor_types
|
||||
|
||||
|
||||
class ModelClassInitializer:
|
||||
api: SemanticAnalyzer
|
||||
|
||||
def __init__(self, ctx: ClassDefContext, django_context: DjangoContext):
|
||||
self.api = ctx.api
|
||||
self.api = cast(SemanticAnalyzer, ctx.api)
|
||||
self.model_classdef = ctx.cls
|
||||
self.django_context = django_context
|
||||
self.ctx = ctx
|
||||
|
||||
def lookup_typeinfo_or_incomplete_defn_error(self, fullname: str) -> TypeInfo:
|
||||
sym = self.api.lookup_fully_qualified_or_none(fullname)
|
||||
if sym is None or not isinstance(sym.node, TypeInfo):
|
||||
raise helpers.IncompleteDefnException(f'No {fullname!r} found')
|
||||
return sym.node
|
||||
def lookup_typeinfo(self, fullname: str) -> Optional[TypeInfo]:
|
||||
return helpers.lookup_fully_qualified_typeinfo(self.api, fullname)
|
||||
|
||||
def lookup_class_typeinfo_or_incomplete_defn_error(self, klass: type) -> TypeInfo:
|
||||
def lookup_typeinfo_or_exception(self, fullname: str) -> TypeInfo:
|
||||
info = self.lookup_typeinfo(fullname)
|
||||
if info is None:
|
||||
raise sem_helpers.IncompleteDefnError(f'No {fullname!r} found')
|
||||
return info
|
||||
|
||||
def lookup_class_typeinfo_or_exception(self, klass: type) -> TypeInfo:
|
||||
fullname = helpers.get_class_fullname(klass)
|
||||
field_info = self.lookup_typeinfo_or_incomplete_defn_error(fullname)
|
||||
field_info = self.lookup_typeinfo_or_exception(fullname)
|
||||
return field_info
|
||||
|
||||
def create_new_var(self, name: str, typ: MypyType) -> Var:
|
||||
# type=: type of the variable itself
|
||||
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.is_initialized_in_class = True
|
||||
var.is_inferred = True
|
||||
return var
|
||||
def model_class_has_attribute_defined(self, name: str, traverse_mro: bool = True) -> bool:
|
||||
if not traverse_mro:
|
||||
sym = self.model_classdef.info.names.get(name)
|
||||
else:
|
||||
sym = self.model_classdef.info.get(name)
|
||||
return sym is not None
|
||||
|
||||
def add_new_node_to_model_class(self, name: str, typ: MypyType) -> None:
|
||||
helpers.add_new_sym_for_info(self.model_classdef.info,
|
||||
name=name,
|
||||
sym_type=typ)
|
||||
def resolve_manager_fullname(self, manager_fullname: str) -> str:
|
||||
base_manager_info = self.lookup_typeinfo(fullnames.MANAGER_CLASS_FULLNAME)
|
||||
if (base_manager_info is None
|
||||
or 'from_queryset_managers' not in base_manager_info.metadata):
|
||||
return manager_fullname
|
||||
|
||||
metadata = base_manager_info.metadata['from_queryset_managers']
|
||||
return metadata.get(manager_fullname, manager_fullname)
|
||||
|
||||
def add_new_node_to_model_class(self, name: str, typ: MypyType,
|
||||
force_replace_existing: bool = False) -> None:
|
||||
if not force_replace_existing and name in self.model_classdef.info.names:
|
||||
raise ValueError(f'Member {name!r} already defined at model {self.model_classdef.info.fullname!r}.')
|
||||
|
||||
var = Var(name, type=typ)
|
||||
# TypeInfo of the object variable is bound to
|
||||
var.info = self.model_classdef.info
|
||||
var._fullname = self.api.qualified_name(name)
|
||||
var.is_initialized_in_class = True
|
||||
|
||||
sym = SymbolTableNode(MDEF, var, plugin_generated=True)
|
||||
context: Optional[Context] = dummy_context()
|
||||
if force_replace_existing:
|
||||
context = None
|
||||
self.api.add_symbol_table_node(name, sym, context=context)
|
||||
|
||||
def add_new_class_for_current_module(self, name: str, bases: List[Instance],
|
||||
force_replace_existing: bool = False) -> TypeInfo:
|
||||
current_module = self.api.cur_mod_node
|
||||
if not force_replace_existing and name in current_module.names:
|
||||
raise ValueError(f'Class {name!r} already defined for module {current_module.fullname!r}')
|
||||
|
||||
new_typeinfo = helpers.new_typeinfo(name,
|
||||
bases=bases,
|
||||
module_name=current_module.fullname)
|
||||
if name in current_module.names:
|
||||
del current_module.names[name]
|
||||
current_module.names[name] = SymbolTableNode(GDEF, new_typeinfo, plugin_generated=True)
|
||||
return new_typeinfo
|
||||
|
||||
def run(self) -> None:
|
||||
model_cls = self.django_context.get_model_class_by_fullname(self.model_classdef.fullname)
|
||||
@@ -78,161 +116,194 @@ class InjectAnyAsBaseForNestedMeta(ModelClassInitializer):
|
||||
"""
|
||||
|
||||
def run(self) -> None:
|
||||
meta_node = helpers.get_nested_meta_node_for_current_class(self.model_classdef.info)
|
||||
meta_node = sem_helpers.get_nested_meta_node_for_current_class(self.model_classdef.info)
|
||||
if meta_node is None:
|
||||
return None
|
||||
meta_node.fallback_to_any = True
|
||||
|
||||
|
||||
class AddDefaultPrimaryKey(ModelClassInitializer):
|
||||
"""
|
||||
Adds default primary key to models which does not define their own.
|
||||
```
|
||||
class User(models.Model):
|
||||
name = models.TextField()
|
||||
```
|
||||
"""
|
||||
|
||||
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
||||
auto_field = model_cls._meta.auto_field
|
||||
if auto_field and not self.model_classdef.info.has_readable_member(auto_field.attname):
|
||||
# autogenerated field
|
||||
auto_field_fullname = helpers.get_class_fullname(auto_field.__class__)
|
||||
auto_field_info = self.lookup_typeinfo_or_incomplete_defn_error(auto_field_fullname)
|
||||
if auto_field is None:
|
||||
return
|
||||
|
||||
set_type, get_type = fields.get_field_descriptor_types(auto_field_info, is_nullable=False)
|
||||
self.add_new_node_to_model_class(auto_field.attname, Instance(auto_field_info,
|
||||
[set_type, get_type]))
|
||||
primary_key_attrname = auto_field.attname
|
||||
if self.model_class_has_attribute_defined(primary_key_attrname):
|
||||
return
|
||||
|
||||
auto_field_class_fullname = helpers.get_class_fullname(auto_field.__class__)
|
||||
auto_field_info = self.lookup_typeinfo_or_exception(auto_field_class_fullname)
|
||||
|
||||
set_type, get_type = fields.get_field_descriptor_types(auto_field_info, is_nullable=False)
|
||||
self.add_new_node_to_model_class(primary_key_attrname, Instance(auto_field_info,
|
||||
[set_type, get_type]))
|
||||
|
||||
|
||||
class AddRelatedModelsId(ModelClassInitializer):
|
||||
"""
|
||||
Adds `FIELDNAME_id` attributes to models.
|
||||
```
|
||||
class User(models.Model):
|
||||
pass
|
||||
class Blog(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
```
|
||||
|
||||
`user_id` will be added to `Blog`.
|
||||
"""
|
||||
|
||||
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
||||
for field in model_cls._meta.get_fields():
|
||||
if isinstance(field, ForeignKey):
|
||||
related_model_cls = self.django_context.get_field_related_model_cls(field)
|
||||
if related_model_cls is None:
|
||||
error_context: Context = self.ctx.cls
|
||||
field_sym = self.ctx.cls.info.get(field.name)
|
||||
if field_sym is not None and field_sym.node is not None:
|
||||
error_context = field_sym.node
|
||||
self.api.fail(f'Cannot find model {field.related_model!r} '
|
||||
f'referenced in field {field.name!r} ',
|
||||
ctx=error_context)
|
||||
self.add_new_node_to_model_class(field.attname,
|
||||
AnyType(TypeOfAny.explicit))
|
||||
if not isinstance(field, (OneToOneField, ForeignKey)):
|
||||
continue
|
||||
related_id_attr_name = field.attname
|
||||
if self.model_class_has_attribute_defined(related_id_attr_name):
|
||||
continue
|
||||
|
||||
related_model_cls = self.django_context.get_field_related_model_cls(field)
|
||||
if related_model_cls is None:
|
||||
error_context: Context = self.ctx.cls
|
||||
field_sym = self.ctx.cls.info.get(field.name)
|
||||
if field_sym is not None and field_sym.node is not None:
|
||||
error_context = field_sym.node
|
||||
self.api.fail(f'Cannot find model {field.related_model!r} '
|
||||
f'referenced in field {field.name!r} ',
|
||||
ctx=error_context)
|
||||
self.add_new_node_to_model_class(related_id_attr_name,
|
||||
AnyType(TypeOfAny.explicit))
|
||||
continue
|
||||
|
||||
if related_model_cls._meta.abstract:
|
||||
continue
|
||||
|
||||
rel_primary_key_field = self.django_context.get_primary_key_field(related_model_cls)
|
||||
try:
|
||||
field_info = self.lookup_class_typeinfo_or_exception(rel_primary_key_field.__class__)
|
||||
except sem_helpers.IncompleteDefnError as exc:
|
||||
if not self.api.final_iteration:
|
||||
raise exc
|
||||
else:
|
||||
continue
|
||||
|
||||
if related_model_cls._meta.abstract:
|
||||
continue
|
||||
|
||||
rel_primary_key_field = self.django_context.get_primary_key_field(related_model_cls)
|
||||
field_info = self.lookup_class_typeinfo_or_incomplete_defn_error(rel_primary_key_field.__class__)
|
||||
is_nullable = self.django_context.get_field_nullability(field, None)
|
||||
set_type, get_type = get_field_descriptor_types(field_info, is_nullable)
|
||||
self.add_new_node_to_model_class(field.attname,
|
||||
Instance(field_info, [set_type, get_type]))
|
||||
is_nullable = self.django_context.get_field_nullability(field, None)
|
||||
set_type, get_type = get_field_descriptor_types(field_info, is_nullable)
|
||||
self.add_new_node_to_model_class(related_id_attr_name,
|
||||
Instance(field_info, [set_type, get_type]))
|
||||
|
||||
|
||||
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
|
||||
|
||||
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
||||
for manager_name, manager in model_cls._meta.managers_map.items():
|
||||
manager_fullname = helpers.get_class_fullname(manager.__class__)
|
||||
manager_info = self.lookup_typeinfo_or_incomplete_defn_error(manager_fullname)
|
||||
if self.model_class_has_attribute_defined(manager_name, traverse_mro=False):
|
||||
sym = self.model_classdef.info.names.get(manager_name)
|
||||
assert sym is not None
|
||||
|
||||
if (sym.type is not None
|
||||
and isinstance(sym.type, Instance)
|
||||
and sym.type.type.has_base(fullnames.BASE_MANAGER_CLASS_FULLNAME)):
|
||||
# already defined and parametrized properly
|
||||
continue
|
||||
|
||||
if getattr(manager, '_built_with_as_manager', False):
|
||||
# as_manager is not supported yet
|
||||
if not self.model_class_has_attribute_defined(manager_name, traverse_mro=True):
|
||||
self.add_new_node_to_model_class(manager_name, AnyType(TypeOfAny.explicit))
|
||||
continue
|
||||
|
||||
manager_fullname = self.resolve_manager_fullname(helpers.get_class_fullname(manager.__class__))
|
||||
manager_info = self.lookup_typeinfo_or_exception(manager_fullname)
|
||||
|
||||
if manager_name not in self.model_classdef.info.names:
|
||||
# manager not yet defined, just add models.Manager[ModelName]
|
||||
manager_type = Instance(manager_info, [Instance(self.model_classdef.info, [])])
|
||||
self.add_new_node_to_model_class(manager_name, manager_type)
|
||||
else:
|
||||
# creates new MODELNAME_MANAGERCLASSNAME class that represents manager parametrized with current model
|
||||
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):
|
||||
if original_base.type is None:
|
||||
raise helpers.IncompleteDefnException()
|
||||
|
||||
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:
|
||||
# add _default_manager
|
||||
if '_default_manager' not in self.model_classdef.info.names:
|
||||
default_manager_fullname = helpers.get_class_fullname(model_cls._meta.default_manager.__class__)
|
||||
default_manager_info = self.lookup_typeinfo_or_incomplete_defn_error(default_manager_fullname)
|
||||
default_manager = Instance(default_manager_info, [Instance(self.model_classdef.info, [])])
|
||||
self.add_new_node_to_model_class('_default_manager', default_manager)
|
||||
if self.model_class_has_attribute_defined('_default_manager', traverse_mro=False):
|
||||
return
|
||||
if model_cls._meta.default_manager is None:
|
||||
return
|
||||
if getattr(model_cls._meta.default_manager, '_built_with_as_manager', False):
|
||||
self.add_new_node_to_model_class('_default_manager',
|
||||
AnyType(TypeOfAny.explicit))
|
||||
return
|
||||
|
||||
default_manager_fullname = helpers.get_class_fullname(model_cls._meta.default_manager.__class__)
|
||||
resolved_default_manager_fullname = self.resolve_manager_fullname(default_manager_fullname)
|
||||
|
||||
default_manager_info = self.lookup_typeinfo_or_exception(resolved_default_manager_fullname)
|
||||
default_manager = Instance(default_manager_info, [Instance(self.model_classdef.info, [])])
|
||||
self.add_new_node_to_model_class('_default_manager', default_manager)
|
||||
|
||||
|
||||
class AddRelatedManagers(ModelClassInitializer):
|
||||
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
||||
# add related managers
|
||||
for relation in self.django_context.get_model_relations(model_cls):
|
||||
attname = relation.get_accessor_name()
|
||||
if attname is None:
|
||||
related_manager_attr_name = relation.get_accessor_name()
|
||||
if related_manager_attr_name is None:
|
||||
# no reverse accessor
|
||||
continue
|
||||
|
||||
if self.model_class_has_attribute_defined(related_manager_attr_name, traverse_mro=False):
|
||||
continue
|
||||
|
||||
related_model_cls = self.django_context.get_field_related_model_cls(relation)
|
||||
if related_model_cls is None:
|
||||
# could not find a referenced model (maybe invalid to= value)
|
||||
continue
|
||||
|
||||
related_model_info = self.lookup_class_typeinfo_or_incomplete_defn_error(related_model_cls)
|
||||
try:
|
||||
related_model_info = self.lookup_class_typeinfo_or_exception(related_model_cls)
|
||||
except sem_helpers.IncompleteDefnError as exc:
|
||||
if not self.api.final_iteration:
|
||||
raise exc
|
||||
else:
|
||||
continue
|
||||
|
||||
if isinstance(relation, OneToOneRel):
|
||||
self.add_new_node_to_model_class(attname, Instance(related_model_info, []))
|
||||
self.add_new_node_to_model_class(related_manager_attr_name, Instance(related_model_info, []))
|
||||
continue
|
||||
|
||||
if isinstance(relation, (ManyToOneRel, ManyToManyRel)):
|
||||
manager_info = self.lookup_typeinfo_or_incomplete_defn_error(fullnames.RELATED_MANAGER_CLASS_FULLNAME)
|
||||
self.add_new_node_to_model_class(attname,
|
||||
Instance(manager_info, [Instance(related_model_info, [])]))
|
||||
continue
|
||||
try:
|
||||
related_manager_info = self.lookup_typeinfo_or_exception(
|
||||
fullnames.RELATED_MANAGER_CLASS) # noqa: E501
|
||||
if 'objects' not in related_model_info.names:
|
||||
raise sem_helpers.IncompleteDefnError()
|
||||
except sem_helpers.IncompleteDefnError as exc:
|
||||
if not self.api.final_iteration:
|
||||
raise exc
|
||||
else:
|
||||
continue
|
||||
|
||||
# create new RelatedManager subclass
|
||||
parametrized_related_manager_type = Instance(related_manager_info,
|
||||
[Instance(related_model_info, [])])
|
||||
default_manager_type = related_model_info.names['objects'].type
|
||||
if (default_manager_type is None
|
||||
or not isinstance(default_manager_type, Instance)
|
||||
or default_manager_type.type.fullname == fullnames.MANAGER_CLASS_FULLNAME):
|
||||
self.add_new_node_to_model_class(related_manager_attr_name, parametrized_related_manager_type)
|
||||
continue
|
||||
|
||||
name = related_model_cls.__name__ + '_' + 'RelatedManager'
|
||||
bases = [parametrized_related_manager_type, default_manager_type]
|
||||
new_related_manager_info = self.add_new_class_for_current_module(name, bases,
|
||||
force_replace_existing=True)
|
||||
self.add_new_node_to_model_class(related_manager_attr_name,
|
||||
Instance(new_related_manager_info, []))
|
||||
|
||||
|
||||
class AddExtraFieldMethods(ModelClassInitializer):
|
||||
@@ -240,7 +311,7 @@ class AddExtraFieldMethods(ModelClassInitializer):
|
||||
# get_FOO_display for choices
|
||||
for field in self.django_context.get_model_fields(model_cls):
|
||||
if field.choices:
|
||||
info = self.lookup_typeinfo_or_incomplete_defn_error('builtins.str')
|
||||
info = self.lookup_typeinfo_or_exception('builtins.str')
|
||||
return_type = Instance(info, [])
|
||||
common.add_method(self.ctx,
|
||||
name='get_{}_display'.format(field.attname),
|
||||
@@ -270,7 +341,7 @@ class AddExtraFieldMethods(ModelClassInitializer):
|
||||
class AddMetaOptionsAttribute(ModelClassInitializer):
|
||||
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
||||
if '_meta' not in self.model_classdef.info.names:
|
||||
options_info = self.lookup_typeinfo_or_incomplete_defn_error(fullnames.OPTIONS_CLASS_FULLNAME)
|
||||
options_info = self.lookup_typeinfo_or_exception(fullnames.OPTIONS_CLASS_FULLNAME)
|
||||
self.add_new_node_to_model_class('_meta',
|
||||
Instance(options_info, [
|
||||
Instance(self.model_classdef.info, [])
|
||||
@@ -292,6 +363,8 @@ def process_model_class(ctx: ClassDefContext,
|
||||
for initializer_cls in initializers:
|
||||
try:
|
||||
initializer_cls(ctx, django_context).run()
|
||||
except helpers.IncompleteDefnException:
|
||||
except sem_helpers.IncompleteDefnError as exc:
|
||||
if not ctx.api.final_iteration:
|
||||
ctx.api.defer()
|
||||
continue
|
||||
raise exc
|
||||
|
||||
@@ -4,7 +4,7 @@ from mypy.types import Type as MypyType
|
||||
from mypy.types import TypeOfAny
|
||||
|
||||
from mypy_django_plugin.django.context import DjangoContext
|
||||
from mypy_django_plugin.lib import fullnames, helpers
|
||||
from mypy_django_plugin.lib import chk_helpers, fullnames, helpers
|
||||
|
||||
|
||||
def typecheck_queryset_filter(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
|
||||
@@ -35,10 +35,10 @@ def typecheck_queryset_filter(ctx: MethodContext, django_context: DjangoContext)
|
||||
fullnames.QUERYSET_CLASS_FULLNAME))):
|
||||
return ctx.default_return_type
|
||||
|
||||
helpers.check_types_compatible(ctx,
|
||||
expected_type=lookup_type,
|
||||
actual_type=provided_type,
|
||||
error_message=f'Incompatible type for lookup {lookup_kwarg!r}:')
|
||||
chk_helpers.check_types_compatible(ctx,
|
||||
expected_type=lookup_type,
|
||||
actual_type=provided_type,
|
||||
error_message=f'Incompatible type for lookup {lookup_kwarg!r}:')
|
||||
|
||||
return ctx.default_return_type
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from collections import OrderedDict
|
||||
from typing import List, Optional, Sequence, Type, Union
|
||||
from typing import List, Optional, Sequence, Type
|
||||
|
||||
from django.core.exceptions import FieldError
|
||||
from django.db.models.base import Model
|
||||
from django.db.models.fields.related import RelatedField
|
||||
from django.db.models.fields.reverse_related import ForeignObjectRel
|
||||
from mypy.nodes import Expression, NameExpr
|
||||
from mypy.plugin import FunctionContext, MethodContext
|
||||
from mypy.types import AnyType, Instance
|
||||
@@ -13,7 +14,7 @@ from mypy.types import TypeOfAny
|
||||
from mypy_django_plugin.django.context import (
|
||||
DjangoContext, LookupsAreUnsupported,
|
||||
)
|
||||
from mypy_django_plugin.lib import fullnames, helpers
|
||||
from mypy_django_plugin.lib import chk_helpers, fullnames, helpers
|
||||
|
||||
|
||||
def _extract_model_type_from_queryset(queryset_type: Instance) -> Optional[Instance]:
|
||||
@@ -29,7 +30,7 @@ def determine_proper_manager_type(ctx: FunctionContext) -> MypyType:
|
||||
default_return_type = ctx.default_return_type
|
||||
assert isinstance(default_return_type, Instance)
|
||||
|
||||
outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
|
||||
outer_model_info = chk_helpers.get_typechecker_api(ctx).scope.active_class()
|
||||
if (outer_model_info is None
|
||||
or not outer_model_info.has_base(fullnames.MODEL_CLASS_FULLNAME)):
|
||||
return default_return_type
|
||||
@@ -47,24 +48,25 @@ def get_field_type_from_lookup(ctx: MethodContext, django_context: DjangoContext
|
||||
except LookupsAreUnsupported:
|
||||
return AnyType(TypeOfAny.explicit)
|
||||
|
||||
if isinstance(lookup_field, RelatedField) and lookup_field.column == lookup:
|
||||
if ((isinstance(lookup_field, RelatedField) and lookup_field.column == lookup)
|
||||
or isinstance(lookup_field, ForeignObjectRel)):
|
||||
related_model_cls = django_context.get_field_related_model_cls(lookup_field)
|
||||
if related_model_cls is None:
|
||||
return AnyType(TypeOfAny.from_error)
|
||||
lookup_field = django_context.get_primary_key_field(related_model_cls)
|
||||
|
||||
field_get_type = django_context.get_field_get_type(helpers.get_typechecker_api(ctx),
|
||||
field_get_type = django_context.get_field_get_type(chk_helpers.get_typechecker_api(ctx),
|
||||
lookup_field, method=method)
|
||||
return field_get_type
|
||||
|
||||
|
||||
def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext, model_cls: Type[Model],
|
||||
flat: bool, named: bool) -> MypyType:
|
||||
field_lookups = resolve_field_lookups(ctx.args[0], ctx, django_context)
|
||||
field_lookups = resolve_field_lookups(ctx.args[0], django_context)
|
||||
if field_lookups is None:
|
||||
return AnyType(TypeOfAny.from_error)
|
||||
|
||||
typechecker_api = helpers.get_typechecker_api(ctx)
|
||||
typechecker_api = chk_helpers.get_typechecker_api(ctx)
|
||||
if len(field_lookups) == 0:
|
||||
if flat:
|
||||
primary_key_field = django_context.get_primary_key_field(model_cls)
|
||||
@@ -78,7 +80,7 @@ def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext,
|
||||
column_type = django_context.get_field_get_type(typechecker_api, field,
|
||||
method='values_list')
|
||||
column_types[field.attname] = column_type
|
||||
return helpers.make_oneoff_named_tuple(typechecker_api, 'Row', column_types)
|
||||
return chk_helpers.make_oneoff_named_tuple(typechecker_api, 'Row', column_types)
|
||||
else:
|
||||
# flat=False, named=False, all fields
|
||||
field_lookups = []
|
||||
@@ -101,9 +103,9 @@ def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext,
|
||||
assert len(column_types) == 1
|
||||
row_type = next(iter(column_types.values()))
|
||||
elif named:
|
||||
row_type = helpers.make_oneoff_named_tuple(typechecker_api, 'Row', column_types)
|
||||
row_type = chk_helpers.make_oneoff_named_tuple(typechecker_api, 'Row', column_types)
|
||||
else:
|
||||
row_type = helpers.make_tuple(typechecker_api, list(column_types.values()))
|
||||
row_type = chk_helpers.make_tuple(typechecker_api, list(column_types.values()))
|
||||
|
||||
return row_type
|
||||
|
||||
@@ -121,13 +123,13 @@ def extract_proper_type_queryset_values_list(ctx: MethodContext, django_context:
|
||||
if model_cls is None:
|
||||
return ctx.default_return_type
|
||||
|
||||
flat_expr = helpers.get_call_argument_by_name(ctx, 'flat')
|
||||
flat_expr = chk_helpers.get_call_argument_by_name(ctx, 'flat')
|
||||
if flat_expr is not None and isinstance(flat_expr, NameExpr):
|
||||
flat = helpers.parse_bool(flat_expr)
|
||||
else:
|
||||
flat = False
|
||||
|
||||
named_expr = helpers.get_call_argument_by_name(ctx, 'named')
|
||||
named_expr = chk_helpers.get_call_argument_by_name(ctx, 'named')
|
||||
if named_expr is not None and isinstance(named_expr, NameExpr):
|
||||
named = helpers.parse_bool(named_expr)
|
||||
else:
|
||||
@@ -146,11 +148,10 @@ def extract_proper_type_queryset_values_list(ctx: MethodContext, django_context:
|
||||
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, row_type])
|
||||
|
||||
|
||||
def resolve_field_lookups(lookup_exprs: Sequence[Expression], ctx: Union[FunctionContext, MethodContext],
|
||||
django_context: DjangoContext) -> Optional[List[str]]:
|
||||
def resolve_field_lookups(lookup_exprs: Sequence[Expression], django_context: DjangoContext) -> Optional[List[str]]:
|
||||
field_lookups = []
|
||||
for field_lookup_expr in lookup_exprs:
|
||||
field_lookup = helpers.resolve_string_attribute_value(field_lookup_expr, ctx, django_context)
|
||||
field_lookup = helpers.resolve_string_attribute_value(field_lookup_expr, django_context)
|
||||
if field_lookup is None:
|
||||
return None
|
||||
field_lookups.append(field_lookup)
|
||||
@@ -170,7 +171,7 @@ def extract_proper_type_queryset_values(ctx: MethodContext, django_context: Djan
|
||||
if model_cls is None:
|
||||
return ctx.default_return_type
|
||||
|
||||
field_lookups = resolve_field_lookups(ctx.args[0], ctx, django_context)
|
||||
field_lookups = resolve_field_lookups(ctx.args[0], django_context)
|
||||
if field_lookups is None:
|
||||
return AnyType(TypeOfAny.from_error)
|
||||
|
||||
@@ -187,5 +188,5 @@ def extract_proper_type_queryset_values(ctx: MethodContext, django_context: Djan
|
||||
|
||||
column_types[field_lookup] = field_lookup_type
|
||||
|
||||
row_type = helpers.make_typeddict(ctx.api, column_types, set(column_types.keys()))
|
||||
row_type = chk_helpers.make_oneoff_typeddict(ctx.api, column_types, set(column_types.keys()))
|
||||
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, row_type])
|
||||
|
||||
@@ -3,13 +3,13 @@ from mypy.types import Instance
|
||||
from mypy.types import Type as MypyType
|
||||
|
||||
from mypy_django_plugin.django.context import DjangoContext
|
||||
from mypy_django_plugin.lib import helpers
|
||||
from mypy_django_plugin.lib import chk_helpers, helpers
|
||||
|
||||
|
||||
def set_auth_user_model_as_type_for_request_user(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
|
||||
auth_user_model = django_context.settings.AUTH_USER_MODEL
|
||||
model_cls = django_context.apps_registry.get_model(auth_user_model)
|
||||
model_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), model_cls)
|
||||
model_info = helpers.lookup_class_typeinfo(chk_helpers.get_typechecker_api(ctx), model_cls)
|
||||
if model_info is None:
|
||||
return ctx.default_attr_type
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from mypy.types import Type as MypyType
|
||||
from mypy.types import TypeOfAny, TypeType
|
||||
|
||||
from mypy_django_plugin.django.context import DjangoContext
|
||||
from mypy_django_plugin.lib import helpers
|
||||
from mypy_django_plugin.lib import chk_helpers, helpers
|
||||
|
||||
|
||||
def get_user_model_hook(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
|
||||
@@ -13,7 +13,7 @@ def get_user_model_hook(ctx: FunctionContext, django_context: DjangoContext) ->
|
||||
model_cls = django_context.apps_registry.get_model(auth_user_model)
|
||||
model_cls_fullname = helpers.get_class_fullname(model_cls)
|
||||
|
||||
model_info = helpers.lookup_fully_qualified_typeinfo(helpers.get_typechecker_api(ctx),
|
||||
model_info = helpers.lookup_fully_qualified_typeinfo(chk_helpers.get_typechecker_api(ctx),
|
||||
model_cls_fullname)
|
||||
if model_info is None:
|
||||
return AnyType(TypeOfAny.unannotated)
|
||||
@@ -28,7 +28,7 @@ def get_type_of_settings_attribute(ctx: AttributeContext, django_context: Django
|
||||
ctx.api.fail(f"'Settings' object has no attribute {setting_name!r}", ctx.context)
|
||||
return ctx.default_attr_type
|
||||
|
||||
typechecker_api = helpers.get_typechecker_api(ctx)
|
||||
typechecker_api = chk_helpers.get_typechecker_api(ctx)
|
||||
|
||||
# first look for the setting in the project settings file, then global settings
|
||||
settings_module = typechecker_api.modules.get(django_context.django_settings_module)
|
||||
|
||||
0
mypy_django_plugin/transformers2/__init__.py
Normal file
0
mypy_django_plugin/transformers2/__init__.py
Normal file
86
mypy_django_plugin/transformers2/dynamic_managers.py
Normal file
86
mypy_django_plugin/transformers2/dynamic_managers.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from typing import Optional
|
||||
|
||||
from mypy.checker import gen_unique_name
|
||||
from mypy.nodes import NameExpr, TypeInfo, SymbolTableNode, StrExpr
|
||||
from mypy.types import Type as MypyType, TypeVarType, TypeVarDef, Instance
|
||||
|
||||
from mypy_django_plugin.lib import helpers, fullnames
|
||||
from mypy_django_plugin.transformers.managers import iter_all_custom_queryset_methods
|
||||
|
||||
|
||||
class CreateNewManagerClassFrom_FromQuerySet(helpers.DynamicClassPluginCallback):
|
||||
def set_manager_mapping(self, runtime_manager_fullname: str, generated_manager_fullname: str) -> None:
|
||||
base_model_info = self.lookup_typeinfo_or_defer(fullnames.MODEL_CLASS_FULLNAME)
|
||||
assert base_model_info is not None
|
||||
managers_metadata = base_model_info.metadata.setdefault('managers', {})
|
||||
managers_metadata[runtime_manager_fullname] = generated_manager_fullname
|
||||
|
||||
def create_typevar_in_current_module(self, name: str,
|
||||
upper_bound: Optional[MypyType] = None) -> TypeVarDef:
|
||||
tvar_name = gen_unique_name(name, self.semanal_api.globals)
|
||||
tvar_def = TypeVarDef(tvar_name,
|
||||
fullname=self.semanal_api.cur_mod_id + '.' + tvar_name,
|
||||
id=-1,
|
||||
values=[],
|
||||
upper_bound=upper_bound)
|
||||
return tvar_def
|
||||
|
||||
def create_new_dynamic_class(self) -> None:
|
||||
# extract Manager class which will act as base
|
||||
callee = self.get_callee()
|
||||
fullname = callee.fullname or callee.expr.fullname
|
||||
callee_manager_info = self.lookup_typeinfo_or_defer(fullname)
|
||||
if callee_manager_info is None:
|
||||
return None
|
||||
|
||||
# extract queryset from which we're going to copy methods
|
||||
passed_queryset_name_expr = self.call_expr.args[0]
|
||||
assert isinstance(passed_queryset_name_expr, NameExpr)
|
||||
queryset_class_name = passed_queryset_name_expr.name
|
||||
sym = self.lookup_same_module_or_defer(queryset_class_name)
|
||||
if sym is None:
|
||||
return None
|
||||
assert isinstance(sym.node, TypeInfo)
|
||||
passed_queryset_info = sym.node
|
||||
|
||||
# for TypeVar bound
|
||||
base_model_info = self.lookup_typeinfo_or_defer(fullnames.MODEL_CLASS_FULLNAME)
|
||||
if base_model_info is None:
|
||||
return
|
||||
model_tvar_defn = self.create_typevar_in_current_module('_M', upper_bound=Instance(base_model_info, []))
|
||||
model_tvar_type = TypeVarType(model_tvar_defn)
|
||||
|
||||
# make Manager[_T]
|
||||
parent_manager_type = Instance(callee_manager_info, [model_tvar_type])
|
||||
|
||||
# instantiate with a proper model, Manager[MyModel], filling all Manager type vars in process
|
||||
queryset_type = Instance(passed_queryset_info, [Instance(base_model_info, [])])
|
||||
new_manager_info = self.new_typeinfo(self.class_name,
|
||||
bases=[queryset_type, parent_manager_type])
|
||||
new_manager_info.defn.type_vars = [model_tvar_defn]
|
||||
new_manager_info.type_vars = [model_tvar_defn.name]
|
||||
new_manager_info.set_line(self.call_expr)
|
||||
|
||||
# copy methods from passed_queryset_info with self type replaced
|
||||
# self_type = Instance(new_manager_info, [model_tvar_type])
|
||||
# for name, method_node in iter_all_custom_queryset_methods(passed_queryset_info):
|
||||
# self.add_method_from_signature(method_node,
|
||||
# name,
|
||||
# self_type,
|
||||
# new_manager_info.defn)
|
||||
|
||||
new_manager_sym = SymbolTableNode(self.semanal_api.current_symbol_kind(),
|
||||
new_manager_info,
|
||||
plugin_generated=True)
|
||||
self.semanal_api.add_symbol_table_node(self.class_name, new_manager_sym)
|
||||
|
||||
# add mapping between generated manager and current one
|
||||
runtime_manager_class_name = None
|
||||
if 'class_name' in self.call_expr.arg_names:
|
||||
class_name_arg = self.call_expr.args[self.call_expr.arg_names.index('class_name')]
|
||||
if isinstance(class_name_arg, StrExpr):
|
||||
runtime_manager_class_name = class_name_arg.value
|
||||
|
||||
new_manager_name = runtime_manager_class_name or (callee_manager_info.name + 'From' + queryset_class_name)
|
||||
self.set_manager_mapping(f'django.db.models.manager.{new_manager_name}',
|
||||
new_manager_info.fullname)
|
||||
303
mypy_django_plugin/transformers2/models.py
Normal file
303
mypy_django_plugin/transformers2/models.py
Normal file
@@ -0,0 +1,303 @@
|
||||
from abc import abstractmethod
|
||||
from typing import Type, Optional
|
||||
|
||||
from django.db.models.base import Model
|
||||
from django.db.models.fields.related import OneToOneField, ForeignKey
|
||||
from mypy.nodes import TypeInfo, Var, SymbolTableNode, MDEF, Argument, ARG_STAR2
|
||||
from mypy.plugin import ClassDefContext
|
||||
from mypy.plugins import common
|
||||
from mypy.semanal import dummy_context
|
||||
from mypy.types import Instance, TypeOfAny, AnyType
|
||||
from mypy.types import Type as MypyType
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.fields import DateField, DateTimeField
|
||||
from mypy_django_plugin.lib import helpers, fullnames, sem_helpers
|
||||
from mypy_django_plugin.transformers import fields
|
||||
from mypy_django_plugin.transformers.fields import get_field_type
|
||||
from mypy_django_plugin.transformers2 import new_helpers
|
||||
|
||||
|
||||
class TransformModelClassCallback(helpers.ClassDefPluginCallback):
|
||||
def get_real_manager_fullname(self, manager_fullname: str) -> str:
|
||||
model_info = self.lookup_typeinfo_or_defer(fullnames.MODEL_CLASS_FULLNAME)
|
||||
real_manager_fullname = model_info.metadata.get('managers', {}).get(manager_fullname, manager_fullname)
|
||||
return real_manager_fullname
|
||||
|
||||
def modify_class_defn(self) -> None:
|
||||
model_cls = self.django_context.get_model_class_by_fullname(self.class_defn.fullname)
|
||||
if model_cls is None:
|
||||
return None
|
||||
return self.modify_model_class_defn(model_cls)
|
||||
|
||||
def add_new_model_attribute(self, name: str, typ: MypyType, force_replace: bool = False) -> None:
|
||||
model_info = self.class_defn.info
|
||||
if name in model_info.names and not force_replace:
|
||||
raise ValueError('Attribute already exists on the model')
|
||||
|
||||
var = Var(name, type=typ)
|
||||
var.info = model_info
|
||||
var._fullname = self.semanal_api.qualified_name(name)
|
||||
var.is_initialized_in_class = True
|
||||
|
||||
sym = SymbolTableNode(MDEF, var, plugin_generated=True)
|
||||
error_context = None if force_replace else dummy_context()
|
||||
added = self.semanal_api.add_symbol_table_node(name, sym, context=error_context)
|
||||
assert added
|
||||
|
||||
def lookup_typeinfo_for_class_or_defer(self, klass: type, *,
|
||||
reason_for_defer: Optional[str] = None) -> Optional[TypeInfo]:
|
||||
manager_cls_fullname = helpers.get_class_fullname(klass)
|
||||
return self.lookup_typeinfo_or_defer(manager_cls_fullname,
|
||||
reason_for_defer=reason_for_defer)
|
||||
|
||||
@abstractmethod
|
||||
def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AddDefaultManagerCallback(TransformModelClassCallback):
|
||||
def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None:
|
||||
if ('_default_manager' in self.class_defn.info.names
|
||||
or runtime_model_cls._meta.default_manager is None):
|
||||
return None
|
||||
|
||||
runtime_default_manager_class = runtime_model_cls._meta.default_manager.__class__
|
||||
runtime_manager_cls_fullname = new_helpers.get_class_fullname(runtime_default_manager_class)
|
||||
manager_cls_fullname = self.get_real_manager_fullname(runtime_manager_cls_fullname)
|
||||
|
||||
default_manager_info = self.lookup_typeinfo_or_defer(manager_cls_fullname)
|
||||
if default_manager_info is None:
|
||||
return
|
||||
|
||||
self.add_new_model_attribute('_default_manager',
|
||||
Instance(default_manager_info, [Instance(self.class_defn.info, [])]))
|
||||
|
||||
|
||||
class AddManagersCallback(TransformModelClassCallback):
|
||||
def modify_model_class_defn(self, runtime_model_cls: Type[models.Model]) -> None:
|
||||
for manager_name, manager in runtime_model_cls._meta.managers_map.items():
|
||||
if manager_name in self.class_defn.info.names:
|
||||
# already defined on the current model class, in file or at a previous iteration
|
||||
continue
|
||||
|
||||
manager_info = self.lookup_typeinfo_for_class_or_defer(manager.__class__)
|
||||
if manager_info is None:
|
||||
continue
|
||||
|
||||
manager_type = Instance(manager_info, [Instance(self.class_defn.info, [])])
|
||||
self.add_new_model_attribute(manager_name, manager_type)
|
||||
|
||||
|
||||
class AddPrimaryKeyIfDoesNotExist(TransformModelClassCallback):
|
||||
"""
|
||||
Adds default primary key to models which does not define their own.
|
||||
class User(models.Model):
|
||||
name = models.TextField()
|
||||
"""
|
||||
|
||||
def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None:
|
||||
auto_pk_field = runtime_model_cls._meta.auto_field
|
||||
if auto_pk_field is None:
|
||||
# defined explicitly
|
||||
return None
|
||||
auto_pk_field_name = auto_pk_field.attname
|
||||
if auto_pk_field_name in self.class_defn.info.names:
|
||||
# added on previous iteration
|
||||
return None
|
||||
|
||||
auto_pk_field_info = self.lookup_typeinfo_for_class_or_defer(auto_pk_field.__class__)
|
||||
if auto_pk_field_info is None:
|
||||
return None
|
||||
|
||||
self.add_new_model_attribute(auto_pk_field_name,
|
||||
fields.get_field_type(auto_pk_field_info, is_nullable=False))
|
||||
|
||||
|
||||
class AddRelatedManagersCallback(TransformModelClassCallback):
|
||||
def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None:
|
||||
for reverse_manager_name, relation in self.django_context.get_model_relations(runtime_model_cls):
|
||||
if (reverse_manager_name is None
|
||||
or reverse_manager_name in self.class_defn.info.names):
|
||||
continue
|
||||
|
||||
self.add_new_model_attribute(reverse_manager_name, AnyType(TypeOfAny.implementation_artifact))
|
||||
#
|
||||
# related_model_cls = self.django_context.get_field_related_model_cls(relation)
|
||||
# if related_model_cls is None:
|
||||
# # could not find a referenced model (maybe invalid to= value, or GenericForeignKey)
|
||||
# continue
|
||||
#
|
||||
# related_model_info = self.lookup_typeinfo_for_class_or_defer(related_model_cls)
|
||||
# if related_model_info is None:
|
||||
# continue
|
||||
#
|
||||
# if isinstance(relation, OneToOneRel):
|
||||
# self.add_new_model_attribute(reverse_manager_name,
|
||||
# Instance(related_model_info, []))
|
||||
# elif isinstance(relation, (ManyToOneRel, ManyToManyRel)):
|
||||
# related_manager_info = self.lookup_typeinfo_or_defer(fullnames.RELATED_MANAGER_CLASS)
|
||||
# if related_manager_info is None:
|
||||
# if not self.defer_till_next_iteration(self.class_defn,
|
||||
# reason=f'{fullnames.RELATED_MANAGER_CLASS!r} is not available for lookup'):
|
||||
# raise TypeInfoNotFound(fullnames.RELATED_MANAGER_CLASS)
|
||||
# continue
|
||||
#
|
||||
# # get type of default_manager for model
|
||||
# default_manager_fullname = helpers.get_class_fullname(related_model_cls._meta.default_manager.__class__)
|
||||
# reason_for_defer = (f'Trying to lookup default_manager {default_manager_fullname!r} '
|
||||
# f'of model {helpers.get_class_fullname(related_model_cls)!r}')
|
||||
# default_manager_info = self.lookup_typeinfo_or_defer(default_manager_fullname,
|
||||
# reason_for_defer=reason_for_defer)
|
||||
# if default_manager_info is None:
|
||||
# continue
|
||||
#
|
||||
# default_manager_type = Instance(default_manager_info, [Instance(related_model_info, [])])
|
||||
#
|
||||
# # related_model_cls._meta.default_manager.__class__
|
||||
# # # we're making a subclass of 'objects', need to have it defined
|
||||
# # if 'objects' not in related_model_info.names:
|
||||
# # if not self.defer_till_next_iteration(self.class_defn,
|
||||
# # reason=f"'objects' manager is not yet defined on {related_model_info.fullname!r}"):
|
||||
# # raise AttributeNotFound(self.class_defn.info, 'objects')
|
||||
# # continue
|
||||
#
|
||||
# related_manager_type = Instance(related_manager_info,
|
||||
# [Instance(related_model_info, [])])
|
||||
# #
|
||||
# # objects_sym = related_model_info.names['objects']
|
||||
# # default_manager_type = objects_sym.type
|
||||
# # if default_manager_type is None:
|
||||
# # # dynamic base class, extract from django_context
|
||||
# # default_manager_cls = related_model_cls._meta.default_manager.__class__
|
||||
# # default_manager_info = self.lookup_typeinfo_for_class_or_defer(default_manager_cls)
|
||||
# # if default_manager_info is None:
|
||||
# # continue
|
||||
# # default_manager_type = Instance(default_manager_info, [Instance(related_model_info, [])])
|
||||
#
|
||||
# if (not isinstance(default_manager_type, Instance)
|
||||
# or default_manager_type.type.fullname == fullnames.MANAGER_CLASS_FULLNAME):
|
||||
# # if not defined or trivial -> just return RelatedManager[Model]
|
||||
# self.add_new_model_attribute(reverse_manager_name, related_manager_type)
|
||||
# continue
|
||||
#
|
||||
# # make anonymous class
|
||||
# name = gen_unique_name(related_model_cls.__name__ + '_' + 'RelatedManager',
|
||||
# self.semanal_api.current_symbol_table())
|
||||
# bases = [related_manager_type, default_manager_type]
|
||||
# new_manager_info = self.new_typeinfo(name, bases)
|
||||
# self.add_new_model_attribute(reverse_manager_name, Instance(new_manager_info, []))
|
||||
|
||||
|
||||
class AddForeignPrimaryKeys(TransformModelClassCallback):
|
||||
def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None:
|
||||
for field in runtime_model_cls._meta.get_fields():
|
||||
if not isinstance(field, (OneToOneField, ForeignKey)):
|
||||
continue
|
||||
rel_pk_field_name = field.attname
|
||||
if rel_pk_field_name in self.class_defn.info.names:
|
||||
continue
|
||||
|
||||
related_model_cls = self.django_context.get_field_related_model_cls(field)
|
||||
if related_model_cls is None:
|
||||
field_sym = self.class_defn.info.get(field.name)
|
||||
if field_sym is not None and field_sym.node is not None:
|
||||
error_context = field_sym.node
|
||||
else:
|
||||
error_context = self.class_defn
|
||||
self.semanal_api.fail(f'Cannot find model {field.related_model!r} '
|
||||
f'referenced in field {field.name!r} ',
|
||||
ctx=error_context)
|
||||
self.add_new_model_attribute(rel_pk_field_name, AnyType(TypeOfAny.from_error))
|
||||
continue
|
||||
if related_model_cls._meta.abstract:
|
||||
continue
|
||||
|
||||
rel_pk_field = self.django_context.get_primary_key_field(related_model_cls)
|
||||
rel_pk_field_info = self.lookup_typeinfo_for_class_or_defer(rel_pk_field.__class__)
|
||||
if rel_pk_field_info is None:
|
||||
continue
|
||||
|
||||
field_type = get_field_type(rel_pk_field_info,
|
||||
is_nullable=self.django_context.get_field_nullability(field))
|
||||
self.add_new_model_attribute(rel_pk_field_name, field_type)
|
||||
|
||||
|
||||
class InjectAnyAsBaseForNestedMeta(TransformModelClassCallback):
|
||||
"""
|
||||
Replaces
|
||||
class MyModel(models.Model):
|
||||
class Meta:
|
||||
pass
|
||||
with
|
||||
class MyModel(models.Model):
|
||||
class Meta(Any):
|
||||
pass
|
||||
to get around incompatible Meta inner classes for different models.
|
||||
"""
|
||||
|
||||
def modify_class_defn(self) -> None:
|
||||
meta_node = sem_helpers.get_nested_meta_node_for_current_class(self.class_defn.info)
|
||||
if meta_node is None:
|
||||
return None
|
||||
meta_node.fallback_to_any = True
|
||||
|
||||
|
||||
class AddMetaOptionsAttribute(TransformModelClassCallback):
|
||||
def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None:
|
||||
if '_meta' not in self.class_defn.info.names:
|
||||
options_info = self.lookup_typeinfo_or_defer(fullnames.OPTIONS_CLASS_FULLNAME)
|
||||
if options_info is not None:
|
||||
self.add_new_model_attribute('_meta',
|
||||
Instance(options_info, [
|
||||
Instance(self.class_defn.info, [])
|
||||
]))
|
||||
|
||||
|
||||
class AddExtraFieldMethods(TransformModelClassCallback):
|
||||
def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None:
|
||||
# get_FOO_display for choices
|
||||
for field in self.django_context.get_model_fields(runtime_model_cls):
|
||||
if field.choices:
|
||||
info = self.lookup_typeinfo_or_defer('builtins.str')
|
||||
return_type = Instance(info, [])
|
||||
common.add_method(self.ctx,
|
||||
name='get_{}_display'.format(field.attname),
|
||||
args=[],
|
||||
return_type=return_type)
|
||||
|
||||
# get_next_by, get_previous_by for Date, DateTime
|
||||
for field in self.django_context.get_model_fields(runtime_model_cls):
|
||||
if isinstance(field, (DateField, DateTimeField)) and not field.null:
|
||||
return_type = Instance(self.class_defn.info, [])
|
||||
common.add_method(self.ctx,
|
||||
name='get_next_by_{}'.format(field.attname),
|
||||
args=[Argument(Var('kwargs', AnyType(TypeOfAny.explicit)),
|
||||
AnyType(TypeOfAny.explicit),
|
||||
initializer=None,
|
||||
kind=ARG_STAR2)],
|
||||
return_type=return_type)
|
||||
common.add_method(self.ctx,
|
||||
name='get_previous_by_{}'.format(field.attname),
|
||||
args=[Argument(Var('kwargs', AnyType(TypeOfAny.explicit)),
|
||||
AnyType(TypeOfAny.explicit),
|
||||
initializer=None,
|
||||
kind=ARG_STAR2)],
|
||||
return_type=return_type)
|
||||
|
||||
|
||||
class ModelCallback(helpers.ClassDefPluginCallback):
|
||||
def __call__(self, ctx: ClassDefContext) -> None:
|
||||
callback_classes = [
|
||||
AddManagersCallback,
|
||||
AddPrimaryKeyIfDoesNotExist,
|
||||
AddForeignPrimaryKeys,
|
||||
AddDefaultManagerCallback,
|
||||
AddRelatedManagersCallback,
|
||||
InjectAnyAsBaseForNestedMeta,
|
||||
AddMetaOptionsAttribute,
|
||||
AddExtraFieldMethods,
|
||||
]
|
||||
for callback_cls in callback_classes:
|
||||
callback = callback_cls(self.plugin)
|
||||
callback.__call__(ctx)
|
||||
30
mypy_django_plugin/transformers2/new_helpers.py
Normal file
30
mypy_django_plugin/transformers2/new_helpers.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from typing import Union
|
||||
|
||||
from mypy.nodes import TypeInfo, MypyFile
|
||||
|
||||
|
||||
class IncompleteDefnError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TypeInfoNotFound(IncompleteDefnError):
|
||||
def __init__(self, fullname: str) -> None:
|
||||
super().__init__(f'It is final iteration and required type {fullname!r} is not ready yet.')
|
||||
|
||||
|
||||
class AttributeNotFound(IncompleteDefnError):
|
||||
def __init__(self, node: Union[TypeInfo, MypyFile], attrname: str) -> None:
|
||||
super().__init__(f'Attribute {attrname!r} is not defined for the {node.fullname!r}.')
|
||||
|
||||
|
||||
class NameNotFound(IncompleteDefnError):
|
||||
def __init__(self, name: str) -> None:
|
||||
super().__init__(f'Could not find {name!r} in the current activated namespaces')
|
||||
|
||||
|
||||
class SymbolAdditionNotPossible(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def get_class_fullname(klass: type) -> str:
|
||||
return klass.__module__ + '.' + klass.__qualname__
|
||||
69
mypy_django_plugin/transformers2/related_managers.py
Normal file
69
mypy_django_plugin/transformers2/related_managers.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from mypy.checker import gen_unique_name
|
||||
from mypy.plugin import AttributeContext
|
||||
from mypy.types import Instance
|
||||
from mypy.types import Type as MypyType
|
||||
|
||||
from django.db.models.fields.reverse_related import ForeignObjectRel, OneToOneRel, ManyToOneRel, ManyToManyRel
|
||||
|
||||
from mypy_django_plugin.lib import helpers, fullnames
|
||||
from mypy_django_plugin.lib.helpers import GetAttributeCallback
|
||||
|
||||
|
||||
class GetRelatedManagerCallback(GetAttributeCallback):
|
||||
obj_type: Instance
|
||||
|
||||
def get_related_manager_type(self, relation: ForeignObjectRel) -> MypyType:
|
||||
related_model_cls = self.django_context.get_field_related_model_cls(relation)
|
||||
if related_model_cls is None:
|
||||
# could not find a referenced model (maybe invalid to= value, or GenericForeignKey)
|
||||
# TODO: show error
|
||||
return self.default_attr_type
|
||||
|
||||
related_model_info = self.lookup_typeinfo(helpers.get_class_fullname(related_model_cls))
|
||||
if related_model_info is None:
|
||||
# TODO: show error
|
||||
return self.default_attr_type
|
||||
|
||||
if isinstance(relation, OneToOneRel):
|
||||
return Instance(related_model_info, [])
|
||||
|
||||
elif isinstance(relation, (ManyToOneRel, ManyToManyRel)):
|
||||
related_manager_info = self.lookup_typeinfo(fullnames.RELATED_MANAGER_CLASS)
|
||||
if related_manager_info is None:
|
||||
return self.default_attr_type
|
||||
|
||||
# get type of default_manager for model
|
||||
default_manager_fullname = helpers.get_class_fullname(related_model_cls._meta.default_manager.__class__)
|
||||
default_manager_info = self.lookup_typeinfo(default_manager_fullname)
|
||||
if default_manager_info is None:
|
||||
return self.default_attr_type
|
||||
|
||||
default_manager_type = Instance(default_manager_info, [Instance(related_model_info, [])])
|
||||
related_manager_type = Instance(related_manager_info,
|
||||
[Instance(related_model_info, [])])
|
||||
|
||||
if (not isinstance(default_manager_type, Instance)
|
||||
or default_manager_type.type.fullname == fullnames.MANAGER_CLASS_FULLNAME):
|
||||
# if not defined or trivial -> just return RelatedManager[Model]
|
||||
return related_manager_type
|
||||
|
||||
# make anonymous class
|
||||
name = gen_unique_name(related_model_cls.__name__ + '_' + 'RelatedManager',
|
||||
self.obj_type.type.names)
|
||||
bases = [related_manager_type, default_manager_type]
|
||||
new_manager_info = self.new_typeinfo(name, bases)
|
||||
return Instance(new_manager_info, [])
|
||||
|
||||
def __call__(self, ctx: AttributeContext):
|
||||
super().__call__(ctx)
|
||||
assert isinstance(self.obj_type, Instance)
|
||||
|
||||
model_fullname = self.obj_type.type.fullname
|
||||
model_cls = self.django_context.get_model_class_by_fullname(model_fullname)
|
||||
if model_cls is None:
|
||||
return self.default_attr_type
|
||||
for reverse_manager_name, relation in self.django_context.get_model_relations(model_cls):
|
||||
if reverse_manager_name == self.name:
|
||||
return self.get_related_manager_type(relation)
|
||||
|
||||
return self.default_attr_type
|
||||
@@ -2,17 +2,17 @@
|
||||
# using this constant.
|
||||
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', 'redirects_tests',
|
||||
'invalid_models_tests', 'i18n', 'migrate_signals', 'model_formsets',
|
||||
IGNORED_MODULES = {'schema', 'gis_tests', 'admin_widgets', 'admin_filters',
|
||||
'sitemaps_tests', 'staticfiles_tests', 'modeladmin',
|
||||
'generic_views', 'forms_tests', 'flatpages_tests',
|
||||
'admin_ordering', 'admin_changelist', 'admin_views',
|
||||
'invalid_models_tests', 'i18n', 'model_formsets',
|
||||
'template_tests', 'template_backends', 'test_runner', 'admin_scripts',
|
||||
'sites_tests', 'inline_formsets', 'foreign_object', 'cache', 'test_client', 'test_client_regress'}
|
||||
'inline_formsets', 'foreign_object', 'cache'}
|
||||
|
||||
MOCK_OBJECTS = ['MockRequest', 'MockCompiler', 'modelz', 'call_count', 'call_args_list',
|
||||
MOCK_OBJECTS = ['MockRequest', 'MockCompiler', 'MockModelAdmin', 'modelz', 'call_count', 'call_args_list',
|
||||
'call_args', 'MockUser', 'Xtemplate', 'DummyRequest', 'DummyUser', 'MinimalUser', 'DummyNode']
|
||||
EXTERNAL_MODULES = ['psycopg2', 'PIL', 'selenium', 'oracle', 'mysql', 'sqlite3', 'sqlparse', 'tblib', 'numpy',
|
||||
EXTERNAL_MODULES = ['psycopg2', 'PIL', 'selenium', 'oracle', 'mysql', 'sqlparse', 'tblib', 'numpy',
|
||||
'bcrypt', 'argon2', 'xml.dom']
|
||||
IGNORED_ERRORS = {
|
||||
'__common__': [
|
||||
@@ -31,7 +31,6 @@ IGNORED_ERRORS = {
|
||||
re.compile(r'has no attribute ("|\')_[a-zA-Z_]+("|\')'),
|
||||
"'Settings' object has no attribute",
|
||||
'**Dict',
|
||||
re.compile(r"Expression of type '.*' is not supported"),
|
||||
'has incompatible type "object"',
|
||||
'undefined in superclass',
|
||||
'Argument after ** must be a mapping',
|
||||
@@ -65,6 +64,10 @@ IGNORED_ERRORS = {
|
||||
'Incompatible types in string interpolation',
|
||||
'"None" has no attribute',
|
||||
'has no attribute "assert',
|
||||
'Unsupported dynamic base class'
|
||||
],
|
||||
'admin_utils': [
|
||||
'"Article" has no attribute "non_field"',
|
||||
],
|
||||
'aggregation': [
|
||||
re.compile(r'got "Optional\[(Author|Publisher)\]", expected "Union\[(Author|Publisher), Combinable\]"'),
|
||||
@@ -130,7 +133,6 @@ IGNORED_ERRORS = {
|
||||
'"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"',
|
||||
],
|
||||
@@ -176,7 +178,6 @@ IGNORED_ERRORS = {
|
||||
],
|
||||
'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"',
|
||||
@@ -229,13 +230,8 @@ IGNORED_ERRORS = {
|
||||
],
|
||||
'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"',
|
||||
@@ -246,7 +242,7 @@ IGNORED_ERRORS = {
|
||||
re.compile(r'"(HttpRequest|WSGIRequest)" has no attribute'),
|
||||
],
|
||||
'many_to_many': [
|
||||
'(expression has type "List[Article]", variable has type "RelatedManager[Article]"',
|
||||
'(expression has type "List[Article]", variable has type "Article_RelatedManager2',
|
||||
'"add" of "RelatedManager" has incompatible type "Article"; expected "Union[Publication, int]"',
|
||||
],
|
||||
'many_to_one': [
|
||||
@@ -259,6 +255,20 @@ IGNORED_ERRORS = {
|
||||
'middleware_exceptions': [
|
||||
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any]"; expected "str"'
|
||||
],
|
||||
'migrate_signals': [
|
||||
'Value of type "Optional[Any]" is not indexable',
|
||||
'Argument 1 to "set" has incompatible type "Optional[Any]"; expected "Iterable[Any]"',
|
||||
],
|
||||
'migrations': [
|
||||
'FakeMigration',
|
||||
'FakeLoader',
|
||||
'"Manager[Any]" has no attribute "args"',
|
||||
'Dict entry 0 has incompatible type "Any"',
|
||||
'Argument 1 to "append" of "list" has incompatible type',
|
||||
'base class "Model" defined the type as "BaseManager[Any]"',
|
||||
'Argument 1 to "RunPython" has incompatible type "str"',
|
||||
|
||||
],
|
||||
'model_fields': [
|
||||
'Item "Field[Any, Any]" of "Union[Field[Any, Any], ForeignObjectRel]" has no attribute',
|
||||
'Incompatible types in assignment (expression has type "Type[Person',
|
||||
@@ -267,6 +277,14 @@ IGNORED_ERRORS = {
|
||||
'Incompatible type for "size" of "FloatModel" (got "object", expected "Union[float, int, str, Combinable]")',
|
||||
'Incompatible type for "value" of "IntegerModel" (got "object", expected',
|
||||
],
|
||||
'model_forms': [
|
||||
'"render" of "Widget"',
|
||||
"Module 'django.core.validators' has no attribute 'ValidationError'",
|
||||
'Incompatible types in assignment',
|
||||
'NewForm',
|
||||
'"type" has no attribute "base_fields"',
|
||||
'Argument "instance" to "InvalidModelForm" has incompatible type "Type[Category]"',
|
||||
],
|
||||
'model_indexes': [
|
||||
'Argument "condition" to "Index" has incompatible type "str"; expected "Optional[Q]"'
|
||||
],
|
||||
@@ -297,7 +315,7 @@ IGNORED_ERRORS = {
|
||||
"'bool' is not a valid base class",
|
||||
],
|
||||
'multiple_database': [
|
||||
'Unexpected attribute "extra_arg" for model "Book"'
|
||||
'Unexpected attribute "extra_arg" for model "Book"',
|
||||
],
|
||||
'null_queries': [
|
||||
"Cannot resolve keyword 'foo' into field"
|
||||
@@ -368,6 +386,9 @@ IGNORED_ERRORS = {
|
||||
'expression has type "CurrentSiteManager[CustomArticle]", base class "AbstractArticle"',
|
||||
"Name 'Optional' is not defined",
|
||||
],
|
||||
'sites_tests': [
|
||||
'"RequestSite" of "Union[Site, RequestSite]" has no attribute "id"',
|
||||
],
|
||||
'syndication_tests': [
|
||||
'List or tuple expected as variable arguments'
|
||||
],
|
||||
@@ -404,6 +425,13 @@ IGNORED_ERRORS = {
|
||||
'"PossessedCar" has no attribute "color"',
|
||||
'expression has type "None", variable has type "List[str]"',
|
||||
],
|
||||
'test_client': [
|
||||
'(expression has type "HttpResponse", variable has type "StreamingHttpResponse")'
|
||||
],
|
||||
'test_client_regress': [
|
||||
'(expression has type "Dict[<nothing>, <nothing>]", variable has type "SessionBase")',
|
||||
'Unsupported left operand type for + ("None")',
|
||||
],
|
||||
'transactions': [
|
||||
'Incompatible types in assignment (expression has type "Thread", variable has type "Callable[[], Any]")'
|
||||
],
|
||||
|
||||
@@ -5,17 +5,17 @@ import sys
|
||||
from argparse import ArgumentParser
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Pattern, Union
|
||||
from typing import Dict, List, Pattern, Tuple, Union
|
||||
|
||||
from git import Repo
|
||||
from git import RemoteProgress, Repo
|
||||
|
||||
from scripts.enabled_test_modules import (
|
||||
EXTERNAL_MODULES, IGNORED_ERRORS, IGNORED_MODULES, MOCK_OBJECTS,
|
||||
)
|
||||
|
||||
DJANGO_COMMIT_REFS = {
|
||||
'2.2': 'e8b0903976077b951795938b260211214ed7fe41',
|
||||
'3.0': '7ec5962638144cbf4c2e47ea7d8dc02d1ce44394'
|
||||
DJANGO_COMMIT_REFS: Dict[str, Tuple[str, str]] = {
|
||||
'2.2': ('stable/2.2.x', '86befcc172c23170a720b3e0c06db51a99b3da59'),
|
||||
'3.0': ('stable/3.0.x', '6cb30414bc0f83b49afc4cae76d4af5656effe9a')
|
||||
}
|
||||
PROJECT_DIRECTORY = Path(__file__).parent.parent
|
||||
DJANGO_SOURCE_DIRECTORY = PROJECT_DIRECTORY / 'django-sources' # type: Path
|
||||
@@ -75,10 +75,21 @@ 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:
|
||||
class ProgressPrinter(RemoteProgress):
|
||||
def line_dropped(self, line: str) -> None:
|
||||
print(line)
|
||||
|
||||
def update(self, op_code, cur_count, max_count=None, message=''):
|
||||
print(self._cur_line)
|
||||
|
||||
|
||||
def get_django_repo_object(branch: str) -> 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)
|
||||
return Repo.clone_from('https://github.com/django/django.git', DJANGO_SOURCE_DIRECTORY,
|
||||
progress=ProgressPrinter(),
|
||||
branch=branch,
|
||||
depth=100)
|
||||
else:
|
||||
repo = Repo(DJANGO_SOURCE_DIRECTORY)
|
||||
return repo
|
||||
@@ -89,10 +100,13 @@ if __name__ == '__main__':
|
||||
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()
|
||||
# install proper Django version
|
||||
subprocess.check_call([sys.executable, '-m', 'pip', 'install', f'Django=={args.django_version}.*'])
|
||||
|
||||
branch, commit_sha = DJANGO_COMMIT_REFS[args.django_version]
|
||||
repo = get_django_repo_object(branch)
|
||||
if repo.head.commit.hexsha != commit_sha:
|
||||
repo.git.fetch('origin')
|
||||
repo.remote('origin').fetch(branch, progress=ProgressPrinter(), depth=100)
|
||||
repo.git.checkout(commit_sha)
|
||||
|
||||
mypy_config_file = (PROJECT_DIRECTORY / 'scripts' / 'mypy.ini').absolute()
|
||||
|
||||
4
setup.py
4
setup.py
@@ -21,14 +21,14 @@ with open('README.md', 'r') as f:
|
||||
readme = f.read()
|
||||
|
||||
dependencies = [
|
||||
'mypy>=0.750,<0.760',
|
||||
'mypy>=0.760,<0.770',
|
||||
'typing-extensions',
|
||||
'django',
|
||||
]
|
||||
|
||||
setup(
|
||||
name="django-stubs",
|
||||
version="1.3.0",
|
||||
version="1.4.0",
|
||||
description='Mypy stubs for Django',
|
||||
long_description=readme,
|
||||
long_description_content_type='text/markdown',
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
pass
|
||||
class Book(models.Model):
|
||||
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
|
||||
owner = models.ForeignKey(db_column='model_id', to='auth.User', on_delete=models.CASCADE)
|
||||
owner = models.ForeignKey(to='auth.User', on_delete=models.CASCADE)
|
||||
|
||||
- case: foreign_key_field_different_order_of_params
|
||||
main: |
|
||||
@@ -648,3 +648,30 @@
|
||||
abstract = True
|
||||
class User(AbstractUser):
|
||||
pass
|
||||
|
||||
|
||||
- case: related_manager_is_a_subclass_of_default_manager
|
||||
main: |
|
||||
from myapp.models import User
|
||||
reveal_type(User().orders) # N: Revealed type is 'main.Order_RelatedManager'
|
||||
reveal_type(User().orders.get()) # N: Revealed type is 'myapp.models.Order*'
|
||||
reveal_type(User().orders.manager_method()) # N: Revealed type is 'builtins.int'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from mypy_django_plugin.lib import generics
|
||||
generics.make_classes_generic(models.Manager)
|
||||
|
||||
class User(models.Model):
|
||||
pass
|
||||
class OrderManager(models.Manager['Order']):
|
||||
def manager_method(self) -> int:
|
||||
pass
|
||||
class Order(models.Model):
|
||||
objects = OrderManager()
|
||||
user = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='orders')
|
||||
|
||||
|
||||
280
test-data/typecheck/managers/querysets/test_from_queryset.yml
Normal file
280
test-data/typecheck/managers/querysets/test_from_queryset.yml
Normal file
@@ -0,0 +1,280 @@
|
||||
- case: from_queryset_with_base_manager
|
||||
main: |
|
||||
from myapp.models import MyModel
|
||||
|
||||
reveal_type(MyModel().objects) # N: Revealed type is 'myapp.models.NewBaseManager[myapp.models.MyModel*]'
|
||||
reveal_type(MyModel().objects.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
||||
reveal_type(MyModel().objects.queryset_method) # N: Revealed type is 'def () -> builtins.str'
|
||||
reveal_type(MyModel().objects.queryset_method()) # N: Revealed type is 'builtins.str'
|
||||
|
||||
reveal_type(MyModel().objects2) # N: Revealed type is 'myapp.models.NewManager[myapp.models.MyModel*]'
|
||||
reveal_type(MyModel().objects2.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
||||
reveal_type(MyModel().objects2.queryset_method) # N: Revealed type is 'def () -> builtins.str'
|
||||
reveal_type(MyModel().objects2.queryset_method()) # N: Revealed type is 'builtins.str'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from typing import TypeVar
|
||||
from django.db import models
|
||||
from django.db.models.manager import BaseManager, Manager
|
||||
from mypy_django_plugin.lib import generics
|
||||
|
||||
generics.make_classes_generic(models.QuerySet)
|
||||
_M = TypeVar('_M', bound=models.Model)
|
||||
|
||||
class ModelQuerySet(models.QuerySet[_M]):
|
||||
def queryset_method(self) -> str:
|
||||
return 'hello'
|
||||
|
||||
NewBaseManager = BaseManager.from_queryset(ModelQuerySet)
|
||||
NewManager = Manager.from_queryset(ModelQuerySet)
|
||||
|
||||
generics.make_classes_generic(NewBaseManager, NewManager)
|
||||
|
||||
class MyModel(models.Model):
|
||||
objects = NewBaseManager['MyModel']()
|
||||
objects2 = NewManager['MyModel']()
|
||||
|
||||
- case: manager_without_generic_requires_annotation
|
||||
main: |
|
||||
from myapp.models import ModelQuerySet
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from django.db.models.manager import BaseManager, Manager
|
||||
from mypy_django_plugin.lib import generics
|
||||
|
||||
class ModelQuerySet(models.QuerySet):
|
||||
def queryset_method(self) -> str:
|
||||
return 'hello'
|
||||
|
||||
NewBaseManager = BaseManager.from_queryset(ModelQuerySet)
|
||||
NewManager = Manager.from_queryset(ModelQuerySet)
|
||||
|
||||
generics.make_classes_generic(NewBaseManager, NewManager)
|
||||
|
||||
class MyModel(models.Model):
|
||||
objects = NewBaseManager() # E: Need type annotation for 'objects'
|
||||
|
||||
|
||||
- case: from_queryset_with_custom_manager_as_base
|
||||
main: |
|
||||
from myapp.models import MyModel, NewManager
|
||||
reveal_type(NewManager()) # N: Revealed type is 'myapp.models.NewManager[<nothing>]'
|
||||
reveal_type(MyModel.objects) # N: Revealed type is 'myapp.models.NewManager[myapp.models.MyModel]'
|
||||
reveal_type(MyModel.objects.get()) # N: Revealed type is 'Any'
|
||||
reveal_type(MyModel.objects.manager_only_method()) # N: Revealed type is 'builtins.int'
|
||||
reveal_type(MyModel.objects.manager_and_queryset_method()) # N: Revealed type is 'builtins.str'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from mypy_django_plugin.lib import generics
|
||||
|
||||
class ModelBaseManager(models.Manager):
|
||||
def manager_only_method(self) -> int:
|
||||
return 1
|
||||
class ModelQuerySet(models.QuerySet):
|
||||
def manager_and_queryset_method(self) -> str:
|
||||
return 'hello'
|
||||
|
||||
NewManager = ModelBaseManager.from_queryset(ModelQuerySet)
|
||||
generics.make_classes_generic(NewManager)
|
||||
|
||||
class MyModel(models.Model):
|
||||
objects = NewManager['MyModel']()
|
||||
|
||||
|
||||
- case: from_queryset_with_class_name_provided
|
||||
main: |
|
||||
from myapp.models import MyModel, NewManager
|
||||
reveal_type(NewManager()) # N: Revealed type is 'myapp.models.NewManager[<nothing>]'
|
||||
reveal_type(MyModel.objects) # N: Revealed type is 'myapp.models.NewManager[myapp.models.MyModel]'
|
||||
reveal_type(MyModel.objects.get()) # N: Revealed type is 'Any'
|
||||
reveal_type(MyModel.objects.manager_only_method()) # N: Revealed type is 'builtins.int'
|
||||
reveal_type(MyModel.objects.manager_and_queryset_method()) # N: Revealed type is 'builtins.str'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from mypy_django_plugin.lib import generics
|
||||
|
||||
class ModelBaseManager(models.Manager):
|
||||
def manager_only_method(self) -> int:
|
||||
return 1
|
||||
class ModelQuerySet(models.QuerySet):
|
||||
def manager_and_queryset_method(self) -> str:
|
||||
return 'hello'
|
||||
|
||||
NewManager = ModelBaseManager.from_queryset(ModelQuerySet, class_name='NewManager')
|
||||
from mypy_django_plugin.lib import generics
|
||||
generics.make_classes_generic(NewManager)
|
||||
|
||||
class MyModel(models.Model):
|
||||
objects = NewManager['MyModel']()
|
||||
|
||||
- case: from_queryset_with_class_inheritance
|
||||
main: |
|
||||
from myapp.models import MyModel
|
||||
reveal_type(MyModel().objects) # N: Revealed type is 'myapp.models.NewManager[myapp.models.MyModel*]'
|
||||
reveal_type(MyModel().objects.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
||||
reveal_type(MyModel().objects.queryset_method()) # N: Revealed type is 'builtins.str'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from django.db.models.manager import BaseManager
|
||||
class BaseQuerySet(models.QuerySet):
|
||||
def queryset_method(self) -> str:
|
||||
return 'hello'
|
||||
class ModelQuerySet(BaseQuerySet):
|
||||
pass
|
||||
|
||||
NewManager = BaseManager.from_queryset(ModelQuerySet)
|
||||
|
||||
from mypy_django_plugin.lib import generics
|
||||
generics.make_classes_generic(NewManager)
|
||||
|
||||
class MyModel(models.Model):
|
||||
objects = NewManager['MyModel']()
|
||||
|
||||
- case: from_queryset_with_manager_in_another_directory_and_imports
|
||||
main: |
|
||||
from myapp.models import MyModel
|
||||
reveal_type(MyModel().objects) # N: Revealed type is 'myapp.managers.NewManager[myapp.models.MyModel*]'
|
||||
reveal_type(MyModel().objects.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
||||
reveal_type(MyModel().objects.queryset_method) # N: Revealed type is 'def (param: Union[builtins.str, None] =) -> Union[builtins.str, None]'
|
||||
reveal_type(MyModel().objects.queryset_method('str')) # N: Revealed type is 'Union[builtins.str, None]'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from myapp.managers import NewManager
|
||||
|
||||
from mypy_django_plugin.lib import generics
|
||||
generics.make_classes_generic(NewManager)
|
||||
|
||||
class MyModel(models.Model):
|
||||
objects = NewManager['MyModel']()
|
||||
- path: myapp/managers.py
|
||||
content: |
|
||||
from typing import Optional
|
||||
from django.db import models
|
||||
|
||||
class ModelQuerySet(models.QuerySet):
|
||||
def queryset_method(self, param: Optional[str] = None) -> Optional[str]:
|
||||
return param
|
||||
|
||||
NewManager = models.Manager.from_queryset(ModelQuerySet)
|
||||
|
||||
- case: from_queryset_with_inherited_manager_and_typing_no_return
|
||||
disable_cache: true
|
||||
main: |
|
||||
from myapp.models import MyModel
|
||||
reveal_type(MyModel().objects) # N: Revealed type is 'myapp.managers.NewManager[myapp.models.MyModel*]'
|
||||
reveal_type(MyModel().objects.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
||||
reveal_type(MyModel().objects.base_queryset_method) # N: Revealed type is 'def (param: Union[builtins.int, builtins.str]) -> <nothing>'
|
||||
reveal_type(MyModel().objects.base_queryset_method(2)) # N: Revealed type is '<nothing>'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from myapp.managers import NewManager
|
||||
|
||||
from mypy_django_plugin.lib import generics
|
||||
generics.make_classes_generic(NewManager)
|
||||
|
||||
class MyModel(models.Model):
|
||||
objects = NewManager['MyModel']()
|
||||
- path: myapp/managers.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from myapp.base_queryset import BaseQuerySet
|
||||
class ModelQuerySet(BaseQuerySet):
|
||||
pass
|
||||
NewManager = models.Manager.from_queryset(ModelQuerySet)
|
||||
- path: myapp/base_queryset.py
|
||||
content: |
|
||||
from typing import NoReturn, Union
|
||||
from django.db import models
|
||||
class BaseQuerySet(models.QuerySet):
|
||||
def base_queryset_method(self, param: Union[int, str]) -> NoReturn:
|
||||
raise ValueError
|
||||
|
||||
|
||||
- case: from_queryset_with_inherited_manager_and_fk_to_auth_contrib
|
||||
disable_cache: true
|
||||
main: |
|
||||
from myapp.base_queryset import BaseQuerySet
|
||||
reveal_type(BaseQuerySet().base_queryset_method) # N: Revealed type is 'def (param: builtins.dict[builtins.str, Union[builtins.int, builtins.str]]) -> Union[builtins.int, builtins.str]'
|
||||
|
||||
from django.contrib.auth.models import Permission
|
||||
reveal_type(Permission().another_models) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.AnotherModelInProjectWithContribAuthM2M]'
|
||||
|
||||
from myapp.managers import NewManager
|
||||
reveal_type(NewManager()) # N: Revealed type is 'myapp.managers.NewManager[<nothing>]'
|
||||
reveal_type(NewManager().base_queryset_method) # N: Revealed type is 'def (param: builtins.dict[builtins.str, Union[builtins.int, builtins.str]]) -> Union[builtins.int, builtins.str]'
|
||||
|
||||
from myapp.models import MyModel
|
||||
reveal_type(MyModel().objects) # N: Revealed type is 'myapp.models.MyModel_NewManager[myapp.models.MyModel]'
|
||||
reveal_type(MyModel().objects.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
||||
reveal_type(MyModel().objects.base_queryset_method) # N: Revealed type is 'def (param: builtins.dict[builtins.str, Union[builtins.int, builtins.str]]) -> Union[builtins.int, builtins.str]'
|
||||
installed_apps:
|
||||
- myapp
|
||||
- django.contrib.auth
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from myapp.managers import NewManager
|
||||
from django.contrib.auth.models import Permission
|
||||
|
||||
from mypy_django_plugin.lib import generics
|
||||
generics.make_classes_generic(NewManager)
|
||||
|
||||
class MyModel(models.Model):
|
||||
objects = NewManager['MyModel']()
|
||||
|
||||
class AnotherModelInProjectWithContribAuthM2M(models.Model):
|
||||
permissions = models.ForeignKey(
|
||||
Permission,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='another_models'
|
||||
)
|
||||
- path: myapp/managers.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from myapp.base_queryset import BaseQuerySet
|
||||
class ModelQuerySet(BaseQuerySet):
|
||||
pass
|
||||
NewManager = models.Manager.from_queryset(ModelQuerySet)
|
||||
- path: myapp/base_queryset.py
|
||||
content: |
|
||||
from typing import Union, Dict
|
||||
from django.db import models
|
||||
class BaseQuerySet(models.QuerySet):
|
||||
def base_queryset_method(self, param: Dict[str, Union[int, str]]) -> Union[int, str]:
|
||||
return param["hello"]
|
||||
@@ -107,3 +107,20 @@
|
||||
class Blog(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
|
||||
|
||||
- case: values_of_many_to_many_field
|
||||
main: |
|
||||
from myapp.models import Author, Book
|
||||
reveal_type(Book.objects.values('authors')) # N: Revealed type is 'django.db.models.query.ValuesQuerySet[myapp.models.Book, TypedDict({'authors': builtins.int})]'
|
||||
reveal_type(Author.objects.values('books')) # N: Revealed type is 'django.db.models.query.ValuesQuerySet[myapp.models.Author, TypedDict({'books': builtins.int})]'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
class Author(models.Model):
|
||||
pass
|
||||
class Book(models.Model):
|
||||
authors = models.ManyToManyField(Author, related_name='books')
|
||||
|
||||
@@ -220,7 +220,27 @@
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from mypy_django_plugin.lib import generics
|
||||
generics.make_classes_generic(models.QuerySet)
|
||||
|
||||
class TransactionQuerySet(models.QuerySet['Transaction']):
|
||||
pass
|
||||
class Transaction(models.Model):
|
||||
total = models.IntegerField()
|
||||
|
||||
- case: values_list_of_many_to_many_field
|
||||
main: |
|
||||
from myapp.models import Author, Book
|
||||
reveal_type(Book.objects.values_list('authors')) # N: Revealed type is 'django.db.models.query.ValuesQuerySet[myapp.models.Book, Tuple[builtins.int]]'
|
||||
reveal_type(Author.objects.values_list('books')) # N: Revealed type is 'django.db.models.query.ValuesQuerySet[myapp.models.Author, Tuple[builtins.int]]'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
class Author(models.Model):
|
||||
pass
|
||||
class Book(models.Model):
|
||||
authors = models.ManyToManyField(Author, related_name='books')
|
||||
@@ -30,6 +30,7 @@
|
||||
class Child(Parent):
|
||||
pass
|
||||
|
||||
|
||||
- case: test_model_objects_attribute_present_in_case_of_model_cls_passed_as_generic_parameter
|
||||
main: |
|
||||
from myapp.models import Base, MyModel
|
||||
@@ -48,30 +49,34 @@
|
||||
class Base(Generic[_T]):
|
||||
def __init__(self, model_cls: Type[_T]):
|
||||
self.model_cls = model_cls
|
||||
reveal_type(self.model_cls._default_manager) # N: Revealed type is 'django.db.models.manager.Manager[django.db.models.base.Model]'
|
||||
reveal_type(self.model_cls._default_manager) # N: Revealed type is 'django.db.models.manager.BaseManager[django.db.models.base.Model]'
|
||||
class MyModel(models.Model):
|
||||
pass
|
||||
class Child(Base[MyModel]):
|
||||
def method(self) -> None:
|
||||
reveal_type(self.model_cls._default_manager) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel]'
|
||||
|
||||
|
||||
- case: if_custom_manager_defined_it_is_set_to_default_manager
|
||||
main: |
|
||||
from myapp.models import MyModel
|
||||
reveal_type(MyModel._default_manager) # N: Revealed type is 'myapp.models.CustomManager[myapp.models.MyModel]'
|
||||
reveal_type(MyModel._default_manager.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from typing import TypeVar
|
||||
from django.db import models
|
||||
_T = TypeVar('_T', bound=models.Model)
|
||||
class CustomManager(models.Manager[_T]):
|
||||
from mypy_django_plugin.lib import generics
|
||||
generics.make_classes_generic(models.Manager)
|
||||
|
||||
class CustomManager(models.Manager['MyModel']):
|
||||
pass
|
||||
class MyModel(models.Model):
|
||||
manager = CustomManager['MyModel']()
|
||||
manager = CustomManager()
|
||||
|
||||
|
||||
- case: if_default_manager_name_is_passed_set_default_manager_to_it
|
||||
main: |
|
||||
@@ -83,40 +88,54 @@
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from typing import TypeVar
|
||||
from django.db import models
|
||||
|
||||
_T = TypeVar('_T', bound=models.Model)
|
||||
class Manager1(models.Manager[_T]):
|
||||
from mypy_django_plugin.lib import generics
|
||||
generics.make_classes_generic(models.Manager)
|
||||
|
||||
class Manager1(models.Manager['MyModel']):
|
||||
pass
|
||||
class Manager2(models.Manager[_T]):
|
||||
class Manager2(models.Manager['MyModel']):
|
||||
pass
|
||||
class MyModel(models.Model):
|
||||
class Meta:
|
||||
default_manager_name = 'm2'
|
||||
m1 = Manager1['MyModel']()
|
||||
m2 = Manager2['MyModel']()
|
||||
m1 = Manager1()
|
||||
m2 = Manager2()
|
||||
|
||||
- case: test_leave_as_is_if_objects_is_set_and_fill_typevars_with_outer_class
|
||||
|
||||
- case: manager_requires_type_annotation_to_be_set_if_generic_is_not_specified
|
||||
main: |
|
||||
from myapp.models import MyUser
|
||||
reveal_type(MyUser.objects) # N: Revealed type is 'myapp.models.UserManager[myapp.models.MyUser]'
|
||||
reveal_type(MyUser.objects.get()) # N: Revealed type is 'myapp.models.MyUser*'
|
||||
reveal_type(MyUser.objects.get_or_404()) # N: Revealed type is 'myapp.models.MyUser'
|
||||
from myapp.models import MyModel
|
||||
reveal_type(MyModel.objects) # N: Revealed type is 'myapp.models.MyManager'
|
||||
reveal_type(MyModel.objects.get()) # N: Revealed type is 'Any'
|
||||
reveal_type(MyModel.objects2) # N: Revealed type is 'myapp.models.MyGenericManager[Any]'
|
||||
reveal_type(MyModel.objects2.get()) # N: Revealed type is 'Any'
|
||||
reveal_type(MyModel.objects3) # N: Revealed type is 'myapp.models.MyGenericManager[myapp.models.MyModel]'
|
||||
reveal_type(MyModel.objects3.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from typing import TypeVar
|
||||
from django.db import models
|
||||
|
||||
class UserManager(models.Manager['MyUser']):
|
||||
def get_or_404(self) -> 'MyUser':
|
||||
pass
|
||||
from mypy_django_plugin.lib import generics
|
||||
generics.make_classes_generic(models.Manager)
|
||||
|
||||
class MyManager(models.Manager):
|
||||
pass
|
||||
_T = TypeVar('_T', bound=models.Model)
|
||||
class MyGenericManager(models.Manager[_T]):
|
||||
pass
|
||||
|
||||
class MyModel(models.Model):
|
||||
objects = MyManager()
|
||||
objects2 = MyGenericManager() # E: Need type annotation for 'objects2'
|
||||
objects3: 'MyGenericManager[MyModel]' = MyGenericManager()
|
||||
|
||||
class MyUser(models.Model):
|
||||
objects = UserManager()
|
||||
|
||||
- case: model_imported_from_different_file
|
||||
main: |
|
||||
@@ -139,13 +158,14 @@
|
||||
class Inventory(models.Model):
|
||||
pass
|
||||
|
||||
|
||||
- case: managers_that_defined_on_other_models_do_not_influence
|
||||
main: |
|
||||
from myapp.models import AbstractPerson, Book
|
||||
reveal_type(AbstractPerson.abstract_persons) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.AbstractPerson]'
|
||||
reveal_type(Book.published_objects) # N: Revealed type is 'myapp.models.PublishedBookManager[myapp.models.Book]'
|
||||
reveal_type(Book.published_objects) # N: Revealed type is 'myapp.models.PublishedBookManager'
|
||||
Book.published_objects.create(title='hello')
|
||||
reveal_type(Book.annotated_objects) # N: Revealed type is 'myapp.models.AnnotatedBookManager[myapp.models.Book]'
|
||||
reveal_type(Book.annotated_objects) # N: Revealed type is 'myapp.models.AnnotatedBookManager'
|
||||
Book.annotated_objects.create(title='hello')
|
||||
installed_apps:
|
||||
- myapp
|
||||
@@ -155,6 +175,9 @@
|
||||
content: |
|
||||
from django.db import models
|
||||
|
||||
from mypy_django_plugin.lib import generics
|
||||
generics.make_classes_generic(models.Manager)
|
||||
|
||||
class AbstractPerson(models.Model):
|
||||
abstract_persons = models.Manager['AbstractPerson']()
|
||||
class PublishedBookManager(models.Manager['Book']):
|
||||
@@ -166,7 +189,8 @@
|
||||
published_objects = PublishedBookManager()
|
||||
annotated_objects = AnnotatedBookManager()
|
||||
|
||||
- case: managers_inherited_from_abstract_classes_multiple_inheritance
|
||||
|
||||
- case: managers_inherited_from_abstract_classes_multiple_inheritance_do_not_warn_on_liskov
|
||||
main: |
|
||||
installed_apps:
|
||||
- myapp
|
||||
@@ -175,6 +199,9 @@
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from mypy_django_plugin.lib import generics
|
||||
generics.make_classes_generic(models.Manager)
|
||||
|
||||
class CustomManager1(models.Manager['AbstractBase1']):
|
||||
pass
|
||||
class AbstractBase1(models.Model):
|
||||
@@ -193,6 +220,7 @@
|
||||
class Child(AbstractBase1, AbstractBase2):
|
||||
pass
|
||||
|
||||
|
||||
- case: model_has_a_manager_of_the_same_type
|
||||
main: |
|
||||
from myapp.models import UnrelatedModel, MyModel
|
||||
@@ -208,59 +236,21 @@
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from mypy_django_plugin.lib import generics
|
||||
generics.make_classes_generic(models.Manager)
|
||||
|
||||
class UnrelatedModel(models.Model):
|
||||
objects = models.Manager['UnrelatedModel']()
|
||||
|
||||
class MyModel(models.Model):
|
||||
pass
|
||||
|
||||
- case: manager_without_annotation_of_the_model_gets_it_from_outer_one
|
||||
main: |
|
||||
from myapp.models import UnrelatedModel2, MyModel2
|
||||
reveal_type(UnrelatedModel2.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.UnrelatedModel2]'
|
||||
reveal_type(UnrelatedModel2.objects.first()) # N: Revealed type is 'Union[myapp.models.UnrelatedModel2*, None]'
|
||||
|
||||
reveal_type(MyModel2.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel2]'
|
||||
reveal_type(MyModel2.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel2*, None]'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
class UnrelatedModel2(models.Model):
|
||||
objects = models.Manager()
|
||||
|
||||
class MyModel2(models.Model):
|
||||
pass
|
||||
|
||||
- case: inherited_manager_has_the_proper_type_of_model
|
||||
main: |
|
||||
from myapp.models import ParentOfMyModel3, MyModel3
|
||||
reveal_type(ParentOfMyModel3.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.ParentOfMyModel3]'
|
||||
reveal_type(ParentOfMyModel3.objects.first()) # N: Revealed type is 'Union[myapp.models.ParentOfMyModel3*, None]'
|
||||
|
||||
reveal_type(MyModel3.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel3]'
|
||||
reveal_type(MyModel3.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel3*, None]'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
class ParentOfMyModel3(models.Model):
|
||||
objects = models.Manager()
|
||||
|
||||
class MyModel3(ParentOfMyModel3):
|
||||
pass
|
||||
|
||||
- case: inheritance_with_explicit_type_on_child_manager
|
||||
main: |
|
||||
from myapp.models import ParentOfMyModel4, MyModel4
|
||||
reveal_type(ParentOfMyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.ParentOfMyModel4]'
|
||||
reveal_type(ParentOfMyModel4.objects.first()) # N: Revealed type is 'Union[myapp.models.ParentOfMyModel4*, None]'
|
||||
reveal_type(ParentOfMyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[Any]'
|
||||
reveal_type(ParentOfMyModel4.objects.first()) # N: Revealed type is 'Union[Any, None]'
|
||||
|
||||
reveal_type(MyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel4]'
|
||||
reveal_type(MyModel4.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel4*, None]'
|
||||
@@ -271,54 +261,22 @@
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from mypy_django_plugin.lib import generics
|
||||
generics.make_classes_generic(models.Manager)
|
||||
|
||||
class ParentOfMyModel4(models.Model):
|
||||
objects = models.Manager()
|
||||
objects = models.Manager() # E: Need type annotation for 'objects'
|
||||
|
||||
class MyModel4(ParentOfMyModel4):
|
||||
objects = models.Manager['MyModel4']()
|
||||
|
||||
# TODO: make it work someday
|
||||
#- case: inheritance_of_two_models_with_custom_objects_manager
|
||||
# main: |
|
||||
# from myapp.models import MyBaseUser, MyUser
|
||||
# reveal_type(MyBaseUser.objects) # N: Revealed type is 'myapp.models.MyBaseManager[myapp.models.MyBaseUser]'
|
||||
# reveal_type(MyBaseUser.objects.get()) # N: Revealed type is 'myapp.models.MyBaseUser'
|
||||
#
|
||||
# reveal_type(MyUser.objects) # N: Revealed type is 'myapp.models.MyManager[myapp.models.MyUser]'
|
||||
# reveal_type(MyUser.objects.get()) # N: Revealed type is 'myapp.models.MyUser'
|
||||
# installed_apps:
|
||||
# - myapp
|
||||
# files:
|
||||
# - path: myapp/__init__.py
|
||||
# - path: myapp/models.py
|
||||
# content: |
|
||||
# from django.db import models
|
||||
#
|
||||
# class MyBaseManager(models.Manager):
|
||||
# pass
|
||||
# class MyBaseUser(models.Model):
|
||||
# objects = MyBaseManager()
|
||||
#
|
||||
# class MyManager(models.Manager):
|
||||
# pass
|
||||
# class MyUser(MyBaseUser):
|
||||
# objects = MyManager()
|
||||
|
||||
- case: custom_manager_returns_proper_model_types
|
||||
- case: manager_defined_as_a_nested_class
|
||||
main: |
|
||||
from myapp.models import 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'
|
||||
from myapp.models import MyModel
|
||||
reveal_type(MyModel.objects) # N: Revealed type is 'myapp.models.MyModel.MyManager'
|
||||
reveal_type(MyModel.objects.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
||||
reveal_type(MyModel.objects.mymethod()) # N: Revealed type is 'builtins.int'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
@@ -326,12 +284,11 @@
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
class MyManager(models.Manager):
|
||||
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()
|
||||
from mypy_django_plugin.lib import generics
|
||||
generics.make_classes_generic(models.Manager)
|
||||
|
||||
class MyModel(models.Model):
|
||||
class MyManager(models.Manager['MyModel']):
|
||||
def mymethod(self) -> int:
|
||||
pass
|
||||
objects = MyManager()
|
||||
@@ -96,6 +96,7 @@
|
||||
import django.contrib.postgres.aggregates.general
|
||||
import django.contrib.postgres.aggregates.mixins
|
||||
import django.contrib.postgres.aggregates.statistics
|
||||
import django.contrib.postgres.constraints
|
||||
import django.contrib.postgres.fields
|
||||
import django.contrib.postgres.fields.array
|
||||
import django.contrib.postgres.fields.citext
|
||||
@@ -269,6 +270,7 @@
|
||||
import django.db.models.constraints
|
||||
import django.db.models.deletion
|
||||
import django.db.models.expressions
|
||||
import django.db.models.enums
|
||||
import django.db.models.fields
|
||||
import django.db.models.fields.files
|
||||
import django.db.models.fields.mixins
|
||||
|
||||
3
test-output/round-table.gv
Normal file
3
test-output/round-table.gv
Normal file
@@ -0,0 +1,3 @@
|
||||
digraph {
|
||||
FuncDef [label="My FuncDef"]
|
||||
}
|
||||
BIN
test-output/round-table.gv.pdf
Normal file
BIN
test-output/round-table.gv.pdf
Normal file
Binary file not shown.
Reference in New Issue
Block a user