convince mypy that user.is_staff (and friends) are booleans (#542)

closes #512

Co-authored-by: proxi <51172302+3n-k1@users.noreply.github.com>
This commit is contained in:
proxi
2020-12-16 15:28:01 -05:00
committed by GitHub
parent cfd9379b58
commit caaa23ab8f
5 changed files with 39 additions and 5 deletions

View File

@@ -1,3 +1,5 @@
ABSTRACT_USER_MODEL_FULLNAME = "django.contrib.auth.models.AbstractUser"
PERMISSION_MIXIN_CLASS_FULLNAME = "django.contrib.auth.models.PermissionsMixin"
MODEL_CLASS_FULLNAME = "django.db.models.base.Model" MODEL_CLASS_FULLNAME = "django.db.models.base.Model"
FIELD_FULLNAME = "django.db.models.fields.Field" FIELD_FULLNAME = "django.db.models.fields.Field"
CHAR_FIELD_FULLNAME = "django.db.models.fields.CharField" CHAR_FIELD_FULLNAME = "django.db.models.fields.CharField"

View File

@@ -22,7 +22,7 @@ from mypy_django_plugin.django.context import DjangoContext
from mypy_django_plugin.lib import fullnames, helpers from mypy_django_plugin.lib import fullnames, helpers
from mypy_django_plugin.transformers import fields, forms, init_create, meta, querysets, request, settings from mypy_django_plugin.transformers import fields, forms, init_create, meta, querysets, request, settings
from mypy_django_plugin.transformers.managers import create_new_manager_class_from_from_queryset_method from mypy_django_plugin.transformers.managers import create_new_manager_class_from_from_queryset_method
from mypy_django_plugin.transformers.models import process_model_class from mypy_django_plugin.transformers.models import process_model_class, set_auth_user_model_boolean_fields
def transform_model_class(ctx: ClassDefContext, django_context: DjangoContext) -> None: def transform_model_class(ctx: ClassDefContext, django_context: DjangoContext) -> None:
@@ -270,8 +270,12 @@ class NewSemanalDjangoPlugin(Plugin):
return partial(settings.get_type_of_settings_attribute, django_context=self.django_context) return partial(settings.get_type_of_settings_attribute, django_context=self.django_context)
info = self._get_typeinfo_or_none(class_name) info = self._get_typeinfo_or_none(class_name)
if info and info.has_base(fullnames.PERMISSION_MIXIN_CLASS_FULLNAME) and attr_name == "is_superuser":
return partial(set_auth_user_model_boolean_fields, django_context=self.django_context)
if info and info.has_base(fullnames.HTTPREQUEST_CLASS_FULLNAME) and attr_name == "user": 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) return partial(request.set_auth_user_model_as_type_for_request_user, django_context=self.django_context)
if info and info.has_base(fullnames.ABSTRACT_USER_MODEL_FULLNAME) and attr_name in ("is_staff", "is_active"):
return partial(set_auth_user_model_boolean_fields, django_context=self.django_context)
return None return None
def get_dynamic_class_hook(self, fullname: str) -> Optional[Callable[[DynamicClassDefContext], None]]: def get_dynamic_class_hook(self, fullname: str) -> Optional[Callable[[DynamicClassDefContext], None]]:

View File

@@ -5,7 +5,7 @@ from django.db.models.fields import DateField, DateTimeField
from django.db.models.fields.related import ForeignKey from django.db.models.fields.related import ForeignKey
from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel, OneToOneRel 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, Argument, Context, FuncDef, TypeInfo, Var
from mypy.plugin import ClassDefContext from mypy.plugin import AttributeContext, ClassDefContext
from mypy.plugins import common from mypy.plugins import common
from mypy.semanal import SemanticAnalyzer from mypy.semanal import SemanticAnalyzer
from mypy.types import AnyType, Instance from mypy.types import AnyType, Instance
@@ -355,3 +355,9 @@ def process_model_class(ctx: ClassDefContext, django_context: DjangoContext) ->
except helpers.IncompleteDefnException: except helpers.IncompleteDefnException:
if not ctx.api.final_iteration: if not ctx.api.final_iteration:
ctx.api.defer() ctx.api.defer()
def set_auth_user_model_boolean_fields(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
boolinfo = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), bool)
assert boolinfo is not None
return Instance(boolinfo, [])

View File

@@ -13,3 +13,25 @@
from django.test import Client from django.test import Client
get_user(Client()) get_user(Client())
- case: test_user_fields
main: |
from typing import Union
from django.contrib.auth.models import AnonymousUser, User
anonymous: AnonymousUser
anonymous_is_staff: bool = anonymous.is_staff
anonymous_is_active: bool = anonymous.is_active
anonymous_is_superuser: bool = anonymous.is_superuser
anonymous_is_authenticated: bool = anonymous.is_authenticated
user: User
user_is_staff: bool = user.is_staff
user_is_active: bool = user.is_active
user_is_superuser: bool = user.is_superuser
user_is_authenticated: bool = user.is_authenticated
union: Union[User, AnonymousUser]
union_is_staff: bool = union.is_staff
union_is_active: bool = union.is_active
union_is_superuser: bool = union.is_superuser
union_is_authenticated: bool = union.is_authenticated

View File

@@ -6,8 +6,8 @@
reveal_type(User().first_name) # N: Revealed type is 'builtins.str*' reveal_type(User().first_name) # N: Revealed type is 'builtins.str*'
reveal_type(User().last_name) # N: Revealed type is 'builtins.str*' reveal_type(User().last_name) # N: Revealed type is 'builtins.str*'
reveal_type(User().email) # N: Revealed type is 'builtins.str*' reveal_type(User().email) # N: Revealed type is 'builtins.str*'
reveal_type(User().is_staff) # N: Revealed type is 'builtins.bool*' reveal_type(User().is_staff) # N: Revealed type is 'builtins.bool'
reveal_type(User().is_active) # N: Revealed type is 'builtins.bool*' reveal_type(User().is_active) # N: Revealed type is 'builtins.bool'
reveal_type(User().date_joined) # N: Revealed type is 'datetime.datetime*' reveal_type(User().date_joined) # N: Revealed type is 'datetime.datetime*'
reveal_type(User().last_login) # N: Revealed type is 'Union[datetime.datetime, None]' reveal_type(User().last_login) # N: Revealed type is 'Union[datetime.datetime, None]'
reveal_type(User().is_authenticated) # N: Revealed type is 'Literal[True]' reveal_type(User().is_authenticated) # N: Revealed type is 'Literal[True]'
@@ -22,7 +22,7 @@
reveal_type(Permission().codename) # N: Revealed type is 'builtins.str*' reveal_type(Permission().codename) # N: Revealed type is 'builtins.str*'
from django.contrib.auth.models import PermissionsMixin from django.contrib.auth.models import PermissionsMixin
reveal_type(PermissionsMixin().is_superuser) # N: Revealed type is 'builtins.bool*' reveal_type(PermissionsMixin().is_superuser) # N: Revealed type is 'builtins.bool'
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
reveal_type(Group().name) # N: Revealed type is 'builtins.str*' reveal_type(Group().name) # N: Revealed type is 'builtins.str*'