* Fix CI

* Fix CI

* Fix CI

* Fix CI

* APply black

* APply black

* Fix mypy

* Fix mypy errors in django-stubs

* Fix format

* Fix plugin

* Do not patch builtins by default

* Fix mypy

* Only run mypy on 3.10 for now

* Only run mypy on 3.10 for now

* WHAT THE HELL

* Enable strict mode in mypy

* Enable strict mode in mypy

* Fix tests

* Fix tests

* Debug

* Debug

* Fix tests

* Fix tests

* Add TYPE_CHECKING debug

* Caching maybe?

* Caching maybe?

* Try explicit `${{ matrix.python-version }}`

* Remove debug

* Fix typing

* Finally
This commit is contained in:
Nikita Sobolev
2022-08-26 13:22:55 +03:00
committed by GitHub
parent d2bfd3710b
commit 0bb1182c42
80 changed files with 223 additions and 582 deletions

View File

@@ -1,3 +1,4 @@
import builtins
from typing import Any, Generic, Iterable, List, Optional, Tuple, Type, TypeVar
from django import VERSION as VERSION
@@ -17,6 +18,8 @@ from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import DeletionMixin, FormMixin
from django.views.generic.list import MultipleObjectMixin
__all__ = ["monkeypatch"]
_T = TypeVar("_T")
_VersionSpec = Tuple[int, int]
@@ -67,7 +70,7 @@ _need_generic: List[MPGeneric[Any]] = [
]
def monkeypatch(extra_classes: Optional[Iterable[type]] = None) -> None:
def monkeypatch(extra_classes: Optional[Iterable[type]] = None, include_builtins: bool = True) -> None:
"""Monkey patch django as necessary to work properly with mypy."""
# Add the __class_getitem__ dunder.
@@ -81,5 +84,7 @@ def monkeypatch(extra_classes: Optional[Iterable[type]] = None) -> None:
for cls in extra_classes:
cls.__class_getitem__ = classmethod(lambda cls, *args, **kwargs: cls) # type: ignore[attr-defined]
__all__ = ["monkeypatch"]
# Add `reveal_type` and `reveal_locals` helpers if needed:
if include_builtins:
builtins.reveal_type = lambda _: None
builtins.reveal_locals = lambda: None

View File

@@ -1,10 +1,7 @@
import sys
from typing import Any
if sys.version_info < (3, 8):
from typing_extensions import Protocol
else:
from typing import Protocol
from typing_extensions import Protocol
# Used internally by mypy_django_plugin.
class AnyAttrAllowed(Protocol):

View File

@@ -12,7 +12,7 @@ dependencies = [
setup(
name="django-stubs-ext",
version="0.5.0",
version="0.6.0",
description="Monkey-patching and extensions for django-stubs",
long_description=readme,
long_description_content_type="text/markdown",
@@ -23,7 +23,7 @@ setup(
maintainer="Nikita Sobolev",
maintainer_email="mail@sobolevn.me",
py_modules=[],
python_requires=">=3.6",
python_requires=">=3.7",
install_requires=dependencies,
packages=["django_stubs_ext", *find_packages(exclude=["scripts"])],
package_data={"django_stubs_ext": ["py.typed"]},
@@ -33,6 +33,7 @@ setup(
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Typing :: Typed",
"Framework :: Django",
"Framework :: Django :: 2.2",

View File

@@ -1,13 +1,12 @@
import builtins
from contextlib import suppress
from typing import Iterable, Optional
from typing import Iterable, List, Optional
import pytest
from _pytest.fixtures import FixtureRequest
from _pytest.monkeypatch import MonkeyPatch
from django.db.models import Model
from django.forms.models import ModelForm
from django.views import View
from typing_extensions import Protocol
import django_stubs_ext
@@ -19,7 +18,10 @@ class _MakeGenericClasses(Protocol):
"""Used to represent a type of ``make_generic_classes`` fixture."""
def __call__(
self, django_version: Optional[_VersionSpec] = None, extra_classes: Optional[Iterable[type]] = None
self,
django_version: Optional[_VersionSpec] = None,
extra_classes: Optional[Iterable[type]] = None,
include_builtins: bool = True,
) -> None:
...
@@ -29,7 +31,7 @@ def make_generic_classes(
request: FixtureRequest,
monkeypatch: MonkeyPatch,
) -> _MakeGenericClasses:
_extra_classes: list[type] = []
_extra_classes: List[type] = []
def fin() -> None:
for el in _need_generic:
@@ -38,13 +40,22 @@ def make_generic_classes(
for cls in _extra_classes:
with suppress(AttributeError):
delattr(cls, "__class_getitem__")
_extra_classes.clear()
with suppress(AttributeError):
del builtins.reveal_type
with suppress(AttributeError):
del builtins.reveal_locals
def factory(django_version: Optional[_VersionSpec] = None, extra_classes: Optional[Iterable[type]] = None) -> None:
def factory(
django_version: Optional[_VersionSpec] = None,
extra_classes: Optional[Iterable[type]] = None,
include_builtins: bool = True,
) -> None:
if extra_classes:
_extra_classes.extend(extra_classes)
if django_version is not None:
monkeypatch.setattr(patch, "VERSION", django_version)
django_stubs_ext.monkeypatch(extra_classes)
django_stubs_ext.monkeypatch(extra_classes=extra_classes, include_builtins=include_builtins)
request.addfinalizer(fin)
return factory
@@ -64,14 +75,17 @@ def test_patched_generics(make_generic_classes: _MakeGenericClasses) -> None:
def test_patched_extra_classes_generics(make_generic_classes: _MakeGenericClasses) -> None:
"""Test that the generics actually get patched for extra classes."""
extra_classes = [View]
class _NotGeneric:
pass
extra_classes = [_NotGeneric]
make_generic_classes(django_version=None, extra_classes=extra_classes)
for cls in extra_classes:
assert cls[type] is cls # type: ignore[misc]
class TestView(View[Model]): # type: ignore[type-arg]
class _TestGeneric(_NotGeneric[Model]): # type: ignore
pass
@@ -106,7 +120,17 @@ def test_mypy_builtins_not_patched_globally(
This should only happend during `django.setup()`
(https://github.com/typeddjango/django-stubs/issues/609).
"""
make_generic_classes()
make_generic_classes(include_builtins=False)
assert not hasattr(builtins, "reveal_type")
assert not hasattr(builtins, "reveal_locals")
def test_mypy_builtins_patched(
make_generic_classes: _MakeGenericClasses,
) -> None:
"""Ensures that builtins are patched by default."""
make_generic_classes()
assert hasattr(builtins, "reveal_type")
assert hasattr(builtins, "reveal_locals")

View File

@@ -3,6 +3,7 @@
# `main` is empty on purpose, because we test the `django.setup()` step.
# It executes the `myapp/models.py` file and should not produce anything,
# because `reveal_type` and `reveal_locals` are patched.
monkeypatch: true
installed_apps:
- myapp
files: