Skip to content

Instantly share code, notes, and snippets.

@asottile-sentry
Created June 16, 2022 19:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save asottile-sentry/c48af22c57b39f4a7cb4f84c08fd4bf4 to your computer and use it in GitHub Desktop.
Save asottile-sentry/c48af22c57b39f4a7cb4f84c08fd4bf4 to your computer and use it in GitHub Desktop.
mypy plugin weirdness
[mypy]
plugins = mypy_plugin.py
[mypy-django.*]
ignore_missing_imports = True
from __future__ import annotations
from typing import Callable
from mypy.plugin import AttributeContext
from mypy.plugin import Plugin
from mypy.types import CallableType
from mypy.types import Instance
from mypy.types import Type
from mypy.types import TypeType
from mypy.types import UnionType
def _get_objects_type(ctx: AttributeContext) -> Type:
def _type_to_res(target_cls: Instance) -> Type:
# TODO: this should check if `target_cls` is a `Model`
real_type = target_cls.type.get('objects')
# could not find the `objects` definition.
# probably not our custom model -- probably a django model
if real_type is None or not isinstance(real_type.type, Instance):
return ctx.default_attr_type
else:
return Instance(real_type.type.type, (target_cls,))
# early-phase parses will treat these as `() -> TheType`
if isinstance(ctx.type, CallableType) and isinstance(ctx.type.ret_type, Instance):
return _type_to_res(ctx.type.ret_type)
# later-phase parses will treat these as `type[TheType]`
elif isinstance(ctx.type, TypeType) and isinstance(ctx.type.item, Instance):
return _type_to_res(ctx.type.item)
# handle unions
elif isinstance(ctx.type, UnionType):
object_types = []
for tp in ctx.type.items:
if isinstance(tp, TypeType) and isinstance(tp.item, Instance):
object_types.append(_type_to_res(tp.item))
else:
ctx.api.fail('objects plugin bug 001', ctx.context)
return ctx.default_attr_type
return UnionType(object_types)
else:
ctx.api.fail('objects plugin bug 002', ctx.context)
return ctx.default_attr_type
class CustomPlugin(Plugin):
def get_class_attribute_hook(
self,
fullname: str,
) -> Callable[[AttributeContext], Type] | None:
if fullname.endswith('.objects'):
return _get_objects_type
else:
return None
def plugin(version: str) -> type[Plugin]:
return CustomPlugin
$ mypy t.py
t.py:34: note: Revealed type is "t.BaseManager[t.Model1]"
t.py:35: note: Revealed type is "t.Model1"
t.py:36: note: Revealed type is "t.CustomManager[t.CustomModel]"
t.py:37: note: Revealed type is "t.CustomModel"
t.py:38: note: Revealed type is "t.CustomModel"
Success: no issues found in 1 source file
$ mypy t.py
t.py:34: note: Revealed type is "t.BaseManager[t.Model1]"
t.py:35: note: Revealed type is "t.Model1"
t.py:36: error: Access to generic instance variables via class is ambiguous
t.py:36: note: Revealed type is "t.BaseManager[t.CustomModel]"
t.py:37: error: Access to generic instance variables via class is ambiguous
t.py:37: note: Revealed type is "t.CustomModel"
t.py:38: error: Access to generic instance variables via class is ambiguous
t.py:38: note: Revealed type is "Any"
Found 3 errors in 1 file (checked 1 source file)
from __future__ import annotations
from typing import Any
from typing import Generic
from typing import TypeVar
from django.db.models.manager import BaseManager as DjangoBaseManager
M = TypeVar('M')
class BaseManager(DjangoBaseManager, Generic[M]):
# uncomment this line for some reason to break everything
# def __init__(self) -> None: ...
def get(self, id: int) -> M: ...
class BaseModel:
objects: BaseManager[Any] = BaseManager()
class Model1(BaseModel):
...
class CustomManager(BaseManager['CustomModel']):
def m(self) -> CustomModel: ...
class CustomModel(BaseModel):
objects = CustomManager()
reveal_type(Model1.objects) # should be `BaseModel[Model1]`
reveal_type(Model1.objects.get(1)) # should be `Model1`
reveal_type(CustomModel.objects) # should be `CustomManager`
reveal_type(CustomModel.objects.get(1)) # should be `CustomModel`
reveal_type(CustomModel.objects.m()) # should be `CustomModel`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment