allow to specify QuerySet with one parameter

This commit is contained in:
Maxim Kurnikov
2019-07-25 19:22:59 +03:00
parent a0a2ecaf46
commit 409c01eb24
5 changed files with 46 additions and 21 deletions

View File

@@ -1,27 +1,25 @@
import os import os
from collections import defaultdict from collections import defaultdict
from contextlib import contextmanager from contextlib import contextmanager
from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Tuple, Type from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Tuple, Type
from mypy.nodes import TypeInfo
from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.db.models.base import Model 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.related import ForeignKey, RelatedField
from django.db.models.fields.reverse_related import ForeignObjectRel from django.db.models.fields.reverse_related import ForeignObjectRel
from django.db.models.sql.query import Query from django.db.models.sql.query import Query
from django.utils.functional import cached_property from django.utils.functional import cached_property
from mypy.checker import TypeChecker from mypy.checker import TypeChecker
from mypy.types import Instance, AnyType, TypeOfAny from mypy.nodes import TypeInfo
from mypy.types import Type as MypyType from mypy.types import AnyType, Instance, Type as MypyType, TypeOfAny
from django.contrib.postgres.fields import ArrayField
from django.db.models.fields import AutoField, CharField, Field
from mypy_django_plugin.lib import helpers from mypy_django_plugin.lib import helpers
if TYPE_CHECKING: if TYPE_CHECKING:
from django.apps.registry import Apps # noqa: F401 from django.apps.registry import Apps # noqa: F401
from django.conf import LazySettings from django.conf import LazySettings # noqa: F401
@contextmanager @contextmanager

View File

@@ -6,9 +6,7 @@ from django.db.models.fields.related import RelatedField
from mypy.errors import Errors from mypy.errors import Errors
from mypy.nodes import MypyFile, TypeInfo from mypy.nodes import MypyFile, TypeInfo
from mypy.options import Options from mypy.options import Options
from mypy.plugin import ( from mypy.plugin import AnalyzeTypeContext, AttributeContext, ClassDefContext, FunctionContext, MethodContext, Plugin
AttributeContext, ClassDefContext, FunctionContext, MethodContext, Plugin,
)
from mypy.types import Type as MypyType from mypy.types import Type as MypyType
from mypy_django_plugin.django.context import DjangoContext from mypy_django_plugin.django.context import DjangoContext
@@ -233,6 +231,15 @@ class NewSemanalDjangoPlugin(Plugin):
return partial(request.set_auth_user_model_as_type_for_request_user, django_context=self.django_context) return partial(request.set_auth_user_model_as_type_for_request_user, django_context=self.django_context)
return None return None
def get_type_analyze_hook(self, fullname: str
) -> Optional[Callable[[AnalyzeTypeContext], MypyType]]:
info = self._get_typeinfo_or_none(fullname)
if (info
and info.has_base(fullnames.QUERYSET_CLASS_FULLNAME)
and not info.has_base(fullnames.BASE_MANAGER_CLASS_FULLNAME)):
return partial(querysets.set_first_generic_param_as_default_for_second, fullname=fullname)
return None
def plugin(version): def plugin(version):
return NewSemanalDjangoPlugin return NewSemanalDjangoPlugin

View File

@@ -1,10 +1,11 @@
from collections import OrderedDict from collections import OrderedDict
from typing import List, Optional, Sequence, Type, Union from typing import List, Optional, Sequence, Type, Union, cast
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.db.models.base import Model from django.db.models.base import Model
from mypy.nodes import Expression, NameExpr from mypy.newsemanal.typeanal import TypeAnalyser
from mypy.plugin import FunctionContext, MethodContext from mypy.nodes import Expression, NameExpr, TypeInfo
from mypy.plugin import FunctionContext, MethodContext, AnalyzeTypeContext
from mypy.types import AnyType, Instance, Type as MypyType, TypeOfAny from mypy.types import AnyType, Instance, Type as MypyType, TypeOfAny
from mypy_django_plugin.django.context import DjangoContext from mypy_django_plugin.django.context import DjangoContext
@@ -169,3 +170,20 @@ def extract_proper_type_queryset_values(ctx: MethodContext, django_context: Djan
row_type = helpers.make_typeddict(ctx.api, column_types, set(column_types.keys())) row_type = helpers.make_typeddict(ctx.api, column_types, set(column_types.keys()))
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, row_type]) return helpers.reparametrize_instance(ctx.default_return_type, [model_type, row_type])
def set_first_generic_param_as_default_for_second(ctx: AnalyzeTypeContext, fullname: str) -> MypyType:
type_analyser_api = cast(TypeAnalyser, ctx.api)
info = helpers.lookup_fully_qualified_typeinfo(type_analyser_api.api, fullname) # type: ignore
assert isinstance(info, TypeInfo)
if not ctx.type.args:
return Instance(info, [AnyType(TypeOfAny.explicit), AnyType(TypeOfAny.explicit)])
args = ctx.type.args
if len(args) == 1:
args = [args[0], args[0]]
analyzed_args = [type_analyser_api.analyze_type(arg) for arg in args]
return Instance(info, analyzed_args)

View File

@@ -1,11 +1,6 @@
from typing import cast
from mypy.checker import TypeChecker
from mypy.nodes import MemberExpr, TypeInfo from mypy.nodes import MemberExpr, TypeInfo
from mypy.plugin import AttributeContext, FunctionContext from mypy.plugin import AttributeContext, FunctionContext
from mypy.types import Instance from mypy.types import Instance, Type as MypyType, TypeType
from mypy.types import Type as MypyType
from mypy.types import TypeType
from mypy_django_plugin.django.context import DjangoContext from mypy_django_plugin.django.context import DjangoContext
from mypy_django_plugin.lib import helpers from mypy_django_plugin.lib import helpers

View File

@@ -29,3 +29,10 @@
class Blog(models.Model): class Blog(models.Model):
created_at = models.DateTimeField() created_at = models.DateTimeField()
- case: queryset_could_be_specified_with_one_type
main: |
from typing import Optional
from django.db import models
queryset: models.QuerySet[models.Model] = models.QuerySet()
reveal_type(queryset) # N: Revealed type is 'django.db.models.query.QuerySet[django.db.models.base.Model, django.db.models.base.Model]'