Files
django-stubs/django_stubs_ext/django_stubs_ext/patch.py
Daniel Hahler ee58b18f15 WIP: django_stubs_ext: monkeypatch reveal_{type,locals} into builtins (#591)
* WIP: django_stubs_ext: monkeypatch `reveal_{type,locals}` into builtins

Fixes https://github.com/typeddjango/django-stubs/issues/590

* fixup! WIP: django_stubs_ext: monkeypatch `reveal_{type,locals}` into builtins

* fixup! fixup! WIP: django_stubs_ext: monkeypatch `reveal_{type,locals}` into builtins

* Update patch.py

Co-authored-by: Nikita Sobolev <mail@sobolevn.me>
2021-04-14 12:33:45 +03:00

67 lines
2.3 KiB
Python

import builtins
from typing import Any, Generic, List, Optional, Tuple, Type, TypeVar
from django import VERSION as VERSION
from django.contrib.admin import ModelAdmin
from django.contrib.admin.options import BaseModelAdmin
from django.db.models.fields import Field
from django.db.models.manager import BaseManager
from django.db.models.query import QuerySet
from django.views.generic.edit import FormMixin
_T = TypeVar("_T")
_VersionSpec = Tuple[int, int]
class MPGeneric(Generic[_T]):
"""Create a data class to hold metadata about the gneric classes needing monkeypatching.
The `version` param is optional, and a value of `None` means that the monkeypatch is
version-independent.
This is slightly overkill for our purposes, but useful for future-proofing against any
possible issues we may run into with this method.
"""
def __init__(self, cls: Type[_T], version: Optional[_VersionSpec] = None):
"""Set the data fields, basic constructor."""
self.version = version
self.cls = cls
def __repr__(self) -> str:
"""Better representation in tests and debug."""
return "<MPGeneric: {}, versions={}>".format(self.cls, self.version or "all")
# certain django classes need to be generic, but lack the __class_getitem__ dunder needed to
# annotate them: https://github.com/typeddjango/django-stubs/issues/507
# this list stores them so `monkeypatch` can fix them when called
_need_generic: List[MPGeneric[Any]] = [
MPGeneric(ModelAdmin),
MPGeneric(FormMixin),
MPGeneric(BaseModelAdmin),
MPGeneric(Field),
# These types do have native `__class_getitem__` method since django 3.1:
MPGeneric(QuerySet, (3, 1)),
MPGeneric(BaseManager, (3, 1)),
]
def monkeypatch() -> None:
"""Monkey patch django as necessary to work properly with mypy."""
# Add the __class_getitem__ dunder.
suited_for_this_version = filter(
lambda spec: spec.version is None or VERSION[:2] <= spec.version,
_need_generic,
)
for el in suited_for_this_version:
el.cls.__class_getitem__ = classmethod(lambda cls, *args, **kwargs: cls)
# Define mypy builtins, to not cause NameError during setting up Django.
builtins.reveal_type = lambda _: None
builtins.reveal_locals = lambda: None
__all__ = ["monkeypatch"]