4 Commits

Author SHA1 Message Date
Maxim Kurnikov
f7e1cfc6c7 bump to 1.3.2 2019-12-13 23:55:08 +03:00
Maksim Kurnikov
d0c25e3bce Allow to run from_queryset() with BaseManager, Manager (#271)
* allow to run from_queryset() with BaseManager, Manager

* fix tests
2019-12-13 20:16:33 +03:00
Maksim Kurnikov
1c31e71ffc enable 'model_forms' for tests typechecking (#270) 2019-12-13 13:30:21 +03:00
Dmitry Groshev
6b3b6be3c1 add .model field to _BaseQuerySet (#268) 2019-12-12 21:50:59 +03:00
14 changed files with 99 additions and 23 deletions

View File

@@ -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.checks.messages import CheckMessage
from django.core.exceptions import ValidationError 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 from django.db.models.options import Options
_Self = TypeVar("_Self", bound="Model") _Self = TypeVar("_Self", bound="Model")
@@ -13,9 +13,9 @@ class Model(metaclass=ModelBase):
class DoesNotExist(Exception): ... class DoesNotExist(Exception): ...
class MultipleObjectsReturned(Exception): ... class MultipleObjectsReturned(Exception): ...
class Meta: ... class Meta: ...
_default_manager: Manager[Model]
_meta: Options[Any] _meta: Options[Any]
objects: Manager[Any] _default_manager: BaseManager[Model]
objects: BaseManager[Any]
pk: Any = ... pk: Any = ...
def __init__(self: _Self, *args, **kwargs) -> None: ... def __init__(self: _Self, *args, **kwargs) -> None: ...
def delete(self, using: Any = ..., keep_parents: bool = ...) -> Tuple[int, Dict[str, int]]: ... def delete(self, using: Any = ..., keep_parents: bool = ...) -> Tuple[int, Dict[str, int]]: ...

View File

@@ -31,6 +31,7 @@ _T = TypeVar("_T", bound=models.Model, covariant=True)
_QS = TypeVar("_QS", bound="_BaseQuerySet") _QS = TypeVar("_QS", bound="_BaseQuerySet")
class _BaseQuerySet(Generic[_T], Sized): class _BaseQuerySet(Generic[_T], Sized):
model: Type[_T]
query: Query query: Query
def __init__( def __init__(
self, self,

View File

@@ -205,10 +205,9 @@ class CallableChoiceIterator:
def __iter__(self) -> None: ... def __iter__(self) -> None: ...
class ChoiceField(Field): class ChoiceField(Field):
choices: Any = ...
def __init__( def __init__(
self, self,
choices: Any = ..., choices: _FieldChoices = ...,
required: bool = ..., required: bool = ...,
widget: Optional[Union[Widget, Type[Widget]]] = ..., widget: Optional[Union[Widget, Type[Widget]]] = ...,
label: Optional[Any] = ..., label: Optional[Any] = ...,

View File

@@ -1,5 +1,21 @@
from datetime import datetime 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 unittest.mock import MagicMock
from uuid import UUID from uuid import UUID
@@ -24,6 +40,11 @@ _Fields = Union[List[Union[Callable, str]], Sequence[str], Literal["__all__"]]
_Labels = Dict[str, str] _Labels = Dict[str, str]
_ErrorMessages = Dict[str, 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( def model_to_dict(
instance: Model, fields: Optional[_Fields] = ..., exclude: Optional[_Fields] = ... instance: Model, fields: Optional[_Fields] = ..., exclude: Optional[_Fields] = ...
) -> Dict[str, Any]: ... ) -> Dict[str, Any]: ...
@@ -76,7 +97,8 @@ class BaseModelForm(BaseForm):
save_m2m: Any = ... save_m2m: Any = ...
def save(self, commit: bool = ...) -> Any: ... def save(self, commit: bool = ...) -> Any: ...
class ModelForm(BaseModelForm): ... class ModelForm(BaseModelForm, metaclass=ModelFormMetaclass):
base_fields: ClassVar[Dict[str, Field]] = ...
def modelform_factory( def modelform_factory(
model: Type[Model], model: Type[Model],

View File

@@ -1,11 +1,12 @@
from typing import Any, Callable, Dict, Optional, Sequence, Type, Union from typing import Any, Callable, Dict, Optional, Sequence, Type, Union
from django.forms.forms import BaseForm 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.base import ContextMixin, TemplateResponseMixin, View
from django.views.generic.detail import BaseDetailView, SingleObjectMixin, SingleObjectTemplateResponseMixin from django.views.generic.detail import BaseDetailView, SingleObjectMixin, SingleObjectTemplateResponseMixin
from typing_extensions import Literal from typing_extensions import Literal
from django.http import HttpRequest, HttpResponse
class FormMixin(ContextMixin): class FormMixin(ContextMixin):
initial: Dict[str, Any] = ... initial: Dict[str, Any] = ...
form_class: Optional[Type[BaseForm]] = ... form_class: Optional[Type[BaseForm]] = ...

View File

@@ -1 +1,2 @@
[mypy] [mypy]
warn_unused_ignores = True

View File

@@ -23,9 +23,7 @@ FORM_MIXIN_CLASS_FULLNAME = 'django.views.generic.edit.FormMixin'
MANAGER_CLASSES = { MANAGER_CLASSES = {
MANAGER_CLASS_FULLNAME, MANAGER_CLASS_FULLNAME,
RELATED_MANAGER_CLASS,
BASE_MANAGER_CLASS_FULLNAME, BASE_MANAGER_CLASS_FULLNAME,
# QUERYSET_CLASS_FULLNAME
} }
RELATED_FIELDS_CLASSES = { RELATED_FIELDS_CLASSES = {

View File

@@ -1,5 +1,5 @@
from mypy.nodes import ( from mypy.nodes import (
GDEF, FuncDef, MemberExpr, NameExpr, StrExpr, SymbolTableNode, TypeInfo, GDEF, FuncDef, MemberExpr, NameExpr, RefExpr, StrExpr, SymbolTableNode, TypeInfo,
) )
from mypy.plugin import ClassDefContext, DynamicClassDefContext from mypy.plugin import ClassDefContext, DynamicClassDefContext
from mypy.types import AnyType, Instance, TypeOfAny from mypy.types import AnyType, Instance, TypeOfAny
@@ -10,9 +10,11 @@ from mypy_django_plugin.lib import helpers
def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefContext) -> None: def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefContext) -> None:
semanal_api = helpers.get_semanal_api(ctx) semanal_api = helpers.get_semanal_api(ctx)
assert isinstance(ctx.call.callee, MemberExpr) callee = ctx.call.callee
assert isinstance(ctx.call.callee.expr, NameExpr) assert isinstance(callee, MemberExpr)
base_manager_info = ctx.call.callee.expr.node assert isinstance(callee.expr, RefExpr)
base_manager_info = callee.expr.node
if base_manager_info is None: if base_manager_info is None:
if not semanal_api.final_iteration: if not semanal_api.final_iteration:
semanal_api.defer() semanal_api.defer()

View File

@@ -144,7 +144,7 @@ class AddManagers(ModelClassInitializer):
return False return False
def is_any_parametrized_manager(self, typ: Instance) -> bool: def is_any_parametrized_manager(self, typ: Instance) -> bool:
return typ.type.fullname == fullnames.MANAGER_CLASS_FULLNAME and isinstance(typ.args[0], AnyType) return typ.type.fullname in fullnames.MANAGER_CLASSES and isinstance(typ.args[0], AnyType)
def get_generated_manager_mappings(self, base_manager_fullname: str) -> Dict[str, str]: def get_generated_manager_mappings(self, base_manager_fullname: str) -> Dict[str, str]:
base_manager_info = self.lookup_typeinfo(base_manager_fullname) base_manager_info = self.lookup_typeinfo(base_manager_fullname)

View File

@@ -3,7 +3,7 @@
import re import re
IGNORED_MODULES = {'schema', 'gis_tests', 'admin_widgets', 'admin_filters', IGNORED_MODULES = {'schema', 'gis_tests', 'admin_widgets', 'admin_filters',
'sitemaps_tests', 'staticfiles_tests', 'modeladmin', 'model_forms', 'sitemaps_tests', 'staticfiles_tests', 'modeladmin',
'generic_views', 'forms_tests', 'flatpages_tests', 'generic_views', 'forms_tests', 'flatpages_tests',
'admin_ordering', 'admin_changelist', 'admin_views', 'admin_ordering', 'admin_changelist', 'admin_views',
'invalid_models_tests', 'i18n', 'model_formsets', 'invalid_models_tests', 'i18n', 'model_formsets',
@@ -271,7 +271,7 @@ IGNORED_ERRORS = {
'"Manager[Any]" has no attribute "args"', '"Manager[Any]" has no attribute "args"',
'Dict entry 0 has incompatible type "Any"', 'Dict entry 0 has incompatible type "Any"',
'Argument 1 to "append" of "list" has incompatible type', 'Argument 1 to "append" of "list" has incompatible type',
'base class "Model" defined the type as "Manager[Any]"', 'base class "Model" defined the type as "BaseManager[Any]"',
'Argument 1 to "RunPython" has incompatible type "str"', 'Argument 1 to "RunPython" has incompatible type "str"',
], ],
@@ -283,6 +283,14 @@ IGNORED_ERRORS = {
'Incompatible type for "size" of "FloatModel" (got "object", expected "Union[float, int, str, Combinable]")', 'Incompatible type for "size" of "FloatModel" (got "object", expected "Union[float, int, str, Combinable]")',
'Incompatible type for "value" of "IntegerModel" (got "object", expected', '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': [ 'model_indexes': [
'Argument "condition" to "Index" has incompatible type "str"; expected "Optional[Q]"' 'Argument "condition" to "Index" has incompatible type "str"; expected "Optional[Q]"'
], ],

View File

@@ -14,8 +14,8 @@ from scripts.enabled_test_modules import (
) )
DJANGO_COMMIT_REFS: Dict[str, Tuple[str, str]] = { DJANGO_COMMIT_REFS: Dict[str, Tuple[str, str]] = {
'2.2': ('stable/2.2.x', 'e8b0903976077b951795938b260211214ed7fe41'), '2.2': ('stable/2.2.x', '86befcc172c23170a720b3e0c06db51a99b3da59'),
'3.0': ('stable/3.0.x', '7ec5962638144cbf4c2e47ea7d8dc02d1ce44394') '3.0': ('stable/3.0.x', '6cb30414bc0f83b49afc4cae76d4af5656effe9a')
} }
PROJECT_DIRECTORY = Path(__file__).parent.parent PROJECT_DIRECTORY = Path(__file__).parent.parent
DJANGO_SOURCE_DIRECTORY = PROJECT_DIRECTORY / 'django-sources' # type: Path DJANGO_SOURCE_DIRECTORY = PROJECT_DIRECTORY / 'django-sources' # type: Path

View File

@@ -28,7 +28,7 @@ dependencies = [
setup( setup(
name="django-stubs", name="django-stubs",
version="1.3.1", version="1.3.2",
description='Mypy stubs for Django', description='Mypy stubs for Django',
long_description=readme, long_description=readme,
long_description_content_type='text/markdown', long_description_content_type='text/markdown',

View File

@@ -1,4 +1,48 @@
- case: test_from_queryset_returns_intersection_of_manager_and_queryset - case: from_queryset_with_base_manager
main: |
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.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 ModelQuerySet(models.QuerySet):
def queryset_method(self) -> str:
return 'hello'
NewManager = BaseManager.from_queryset(ModelQuerySet)
class MyModel(models.Model):
objects = NewManager()
- case: from_queryset_with_manager
main: |
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.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
class ModelQuerySet(models.QuerySet):
def queryset_method(self) -> str:
return 'hello'
NewManager = models.Manager.from_queryset(ModelQuerySet)
class MyModel(models.Model):
objects = NewManager()
- case: from_queryset_returns_intersection_of_manager_and_queryset
main: | main: |
from myapp.models import MyModel, NewManager from myapp.models import MyModel, NewManager
reveal_type(NewManager()) # N: Revealed type is 'myapp.models.NewManager' reveal_type(NewManager()) # N: Revealed type is 'myapp.models.NewManager'
@@ -24,7 +68,7 @@
class MyModel(models.Model): class MyModel(models.Model):
objects = NewManager() objects = NewManager()
- case: test_from_queryset_with_class_name_provided - case: from_queryset_with_class_name_provided
main: | main: |
from myapp.models import MyModel, NewManager from myapp.models import MyModel, NewManager
reveal_type(NewManager()) # N: Revealed type is 'myapp.models.NewManager' reveal_type(NewManager()) # N: Revealed type is 'myapp.models.NewManager'

View File

@@ -48,7 +48,7 @@
class Base(Generic[_T]): class Base(Generic[_T]):
def __init__(self, model_cls: Type[_T]): def __init__(self, model_cls: Type[_T]):
self.model_cls = model_cls 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): class MyModel(models.Model):
pass pass
class Child(Base[MyModel]): class Child(Base[MyModel]):