1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
|
import builtins
from collections.abc import Iterable
from contextlib import suppress
from typing import Protocol
import pytest
from _pytest.fixtures import FixtureRequest
from _pytest.monkeypatch import MonkeyPatch
from django.db.models import Model
from django.forms.models import ModelForm
import django_stubs_ext
from django_stubs_ext import patch
from django_stubs_ext.patch import _need_generic, _VersionSpec
class _MakeGenericClasses(Protocol):
"""Used to represent a type of ``make_generic_classes`` fixture."""
def __call__(
self,
django_version: _VersionSpec | None = None,
extra_classes: Iterable[type] | None = None,
include_builtins: bool = True,
) -> None: ...
@pytest.fixture(scope="function")
def make_generic_classes(
request: FixtureRequest,
monkeypatch: MonkeyPatch,
) -> _MakeGenericClasses:
_extra_classes: list[type] = []
def fin() -> None:
for el in _need_generic:
with suppress(AttributeError):
delattr(el.cls, "__class_getitem__")
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: _VersionSpec | None = None,
extra_classes: Iterable[type] | None = 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=extra_classes, include_builtins=include_builtins)
request.addfinalizer(fin)
return factory
def test_patched_generics(make_generic_classes: _MakeGenericClasses) -> None:
"""Test that the generics actually get patched."""
make_generic_classes()
for el in _need_generic:
if el.version is None:
assert el.cls[type] is el.cls # `type` is arbitrary
class TestForm(ModelForm[Model]):
pass
def test_patched_extra_classes_generics(make_generic_classes: _MakeGenericClasses) -> None:
"""Test that the generics actually get patched for extra classes."""
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 _TestGeneric(_NotGeneric[Model]): # type: ignore[type-arg]
pass
@pytest.mark.parametrize(
"django_version",
[
(2, 2),
(3, 0),
(3, 1),
(3, 2),
(4, 0),
(4, 1),
],
)
def test_patched_version_specific(
django_version: _VersionSpec,
make_generic_classes: _MakeGenericClasses,
) -> None:
"""Test version specific types."""
make_generic_classes(django_version)
for el in _need_generic:
if el.version is not None and django_version <= el.version:
assert el.cls[int] is el.cls
def test_mypy_builtins_not_patched_globally(
make_generic_classes: _MakeGenericClasses,
) -> None:
"""Ensures that builtins are not patched with `mypy` specific helpers.
This should only happen during `django.setup()`
(https://github.com/typeddjango/django-stubs/issues/609).
"""
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")
|