mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-15 00:07:09 +08:00
Adds more types to patch
This commit is contained in:
@@ -42,6 +42,7 @@ We rely on different `django` and `mypy` versions:
|
|||||||
|
|
||||||
| django-stubs | mypy version | django version | python version
|
| django-stubs | mypy version | django version | python version
|
||||||
| ------------ | ---- | ---- | ---- |
|
| ------------ | ---- | ---- | ---- |
|
||||||
|
| 1.7.0 | 0.790 | 2.2.x \|\| 3.x | ^3.6
|
||||||
| 1.6.0 | 0.780 | 2.2.x \|\| 3.x | ^3.6
|
| 1.6.0 | 0.780 | 2.2.x \|\| 3.x | ^3.6
|
||||||
| 1.5.0 | 0.770 | 2.2.x \|\| 3.x | ^3.6
|
| 1.5.0 | 0.770 | 2.2.x \|\| 3.x | ^3.6
|
||||||
| 1.4.0 | 0.760 | 2.2.x \|\| 3.x | ^3.6
|
| 1.4.0 | 0.760 | 2.2.x \|\| 3.x | ^3.6
|
||||||
@@ -77,11 +78,13 @@ option to get extra information about the error.
|
|||||||
### I cannot use QuerySet or Manager with type annotations
|
### I cannot use QuerySet or Manager with type annotations
|
||||||
|
|
||||||
You can get a `TypeError: 'type' object is not subscriptable`
|
You can get a `TypeError: 'type' object is not subscriptable`
|
||||||
when you will try to use `QuerySet[MyModel]` or `Manager[MyModel]`.
|
when you will try to use `QuerySet[MyModel]`, `Manager[MyModel]` or some other Django-based Generic types.
|
||||||
|
|
||||||
This happens because Django classes do not support [`__class_getitem__`](https://www.python.org/dev/peps/pep-0560/#class-getitem) magic method.
|
This happens because these Django classes do not support [`__class_getitem__`](https://www.python.org/dev/peps/pep-0560/#class-getitem) magic method in runtime.
|
||||||
|
|
||||||
You can use strings instead: `'QuerySet[MyModel]'` and `'Manager[MyModel]'`, this way it will work as a type for `mypy` and as a regular `str` in runtime.
|
1. You can go with our
|
||||||
|
|
||||||
|
2. You can use strings instead: `'QuerySet[MyModel]'` and `'Manager[MyModel]'`, this way it will work as a type for `mypy` and as a regular `str` in runtime.
|
||||||
|
|
||||||
Currently we [are working](https://github.com/django/django/pull/12405) on providing `__class_getitem__` to the classes where we need them.
|
Currently we [are working](https://github.com/django/django/pull/12405) on providing `__class_getitem__` to the classes where we need them.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
from django.core.management.base import BaseCommand as BaseCommand, CommandError as CommandError, CommandParser as CommandParser
|
from django.core.management.base import (
|
||||||
|
BaseCommand as BaseCommand,
|
||||||
|
CommandError as CommandError,
|
||||||
|
CommandParser as CommandParser,
|
||||||
|
)
|
||||||
from django.core.management.utils import find_command as find_command, popen_wrapper as popen_wrapper
|
from django.core.management.utils import find_command as find_command, popen_wrapper as popen_wrapper
|
||||||
from typing import List, Tuple, Union
|
from typing import List, Tuple, Union
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,14 @@ from django.conf import settings as settings
|
|||||||
from django.core.cache import caches as caches
|
from django.core.cache import caches as caches
|
||||||
from django.core.cache.backends.db import BaseDatabaseCache as BaseDatabaseCache
|
from django.core.cache.backends.db import BaseDatabaseCache as BaseDatabaseCache
|
||||||
from django.core.management.base import BaseCommand as BaseCommand, CommandError as CommandError
|
from django.core.management.base import BaseCommand as BaseCommand, CommandError as CommandError
|
||||||
from django.db import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS, DatabaseError as DatabaseError, connections as connections, models as models, router as router, transaction as transaction
|
from django.db import (
|
||||||
|
DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS,
|
||||||
|
DatabaseError as DatabaseError,
|
||||||
|
connections as connections,
|
||||||
|
models as models,
|
||||||
|
router as router,
|
||||||
|
transaction as transaction,
|
||||||
|
)
|
||||||
from typing import Any, List
|
from typing import Any, List
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
from django.core.management.base import BaseCommand as BaseCommand, CommandError as CommandError, CommandParser as CommandParser
|
from django.core.management.base import (
|
||||||
|
BaseCommand as BaseCommand,
|
||||||
|
CommandError as CommandError,
|
||||||
|
CommandParser as CommandParser,
|
||||||
|
)
|
||||||
from django.db import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS, connections as connections
|
from django.db import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS, connections as connections
|
||||||
|
|
||||||
class Command(BaseCommand): ...
|
class Command(BaseCommand): ...
|
||||||
|
|||||||
@@ -4,5 +4,9 @@ from typing import Any, Callable, Dict, List
|
|||||||
def module_to_dict(module: Any, omittable: Callable[[str], bool] = ...) -> Dict[str, str]: ...
|
def module_to_dict(module: Any, omittable: Callable[[str], bool] = ...) -> Dict[str, str]: ...
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
def output_hash(self, user_settings: Dict[str, str], default_settings: Dict[str, str], **options: Any) -> List[str]: ...
|
def output_hash(
|
||||||
def output_unified(self, user_settings: Dict[str, str], default_settings: Dict[str, str], **options: Any) -> List[str]: ...
|
self, user_settings: Dict[str, str], default_settings: Dict[str, str], **options: Any
|
||||||
|
) -> List[str]: ...
|
||||||
|
def output_unified(
|
||||||
|
self, user_settings: Dict[str, str], default_settings: Dict[str, str], **options: Any
|
||||||
|
) -> List[str]: ...
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ class Command(BaseCommand):
|
|||||||
stealth_options: Tuple[str] = ...
|
stealth_options: Tuple[str] = ...
|
||||||
db_module: str = ...
|
db_module: str = ...
|
||||||
def handle_inspection(self, options: Dict[str, Any]) -> Iterable[str]: ...
|
def handle_inspection(self, options: Dict[str, Any]) -> Iterable[str]: ...
|
||||||
def normalize_col_name(self, col_name: str, used_column_names: List[str], is_relation: bool) -> Tuple[str, Dict[str, str], List[str]]: ...
|
def normalize_col_name(
|
||||||
|
self, col_name: str, used_column_names: List[str], is_relation: bool
|
||||||
|
) -> Tuple[str, Dict[str, str], List[str]]: ...
|
||||||
def get_field_type(self, connection: Any, table_name: Any, row: Any) -> Tuple[str, Dict[str, str], List[str]]: ...
|
def get_field_type(self, connection: Any, table_name: Any, row: Any) -> Tuple[str, Dict[str, str], List[str]]: ...
|
||||||
def get_meta(self, table_name: str, constraints: Any, column_to_field_name: Any, is_view: Any, is_partition: Any) -> List[str]: ...
|
def get_meta(
|
||||||
|
self, table_name: str, constraints: Any, column_to_field_name: Any, is_view: Any, is_partition: Any
|
||||||
|
) -> List[str]: ...
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
from django.apps import apps as apps
|
from django.apps import apps as apps
|
||||||
from django.conf import settings as settings
|
from django.conf import settings as settings
|
||||||
from django.core.management.base import BaseCommand as BaseCommand, CommandError as CommandError, no_translations as no_translations
|
from django.core.management.base import (
|
||||||
from django.db import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS, OperationalError as OperationalError, connections as connections, router as router
|
BaseCommand as BaseCommand,
|
||||||
|
CommandError as CommandError,
|
||||||
|
no_translations as no_translations,
|
||||||
|
)
|
||||||
|
from django.db import (
|
||||||
|
DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS,
|
||||||
|
OperationalError as OperationalError,
|
||||||
|
connections as connections,
|
||||||
|
router as router,
|
||||||
|
)
|
||||||
from django.db.migrations import Migration as Migration
|
from django.db.migrations import Migration as Migration
|
||||||
from django.db.migrations.autodetector import MigrationAutodetector as MigrationAutodetector
|
from django.db.migrations.autodetector import MigrationAutodetector as MigrationAutodetector
|
||||||
from django.db.migrations.loader import MigrationLoader as MigrationLoader
|
from django.db.migrations.loader import MigrationLoader as MigrationLoader
|
||||||
from django.db.migrations.questioner import InteractiveMigrationQuestioner as InteractiveMigrationQuestioner, MigrationQuestioner as MigrationQuestioner, NonInteractiveMigrationQuestioner as NonInteractiveMigrationQuestioner
|
from django.db.migrations.questioner import (
|
||||||
|
InteractiveMigrationQuestioner as InteractiveMigrationQuestioner,
|
||||||
|
MigrationQuestioner as MigrationQuestioner,
|
||||||
|
NonInteractiveMigrationQuestioner as NonInteractiveMigrationQuestioner,
|
||||||
|
)
|
||||||
from django.db.migrations.state import ProjectState as ProjectState
|
from django.db.migrations.state import ProjectState as ProjectState
|
||||||
from django.db.migrations.utils import get_migration_name_timestamp as get_migration_name_timestamp
|
from django.db.migrations.utils import get_migration_name_timestamp as get_migration_name_timestamp
|
||||||
from django.db.migrations.writer import MigrationWriter as MigrationWriter
|
from django.db.migrations.writer import MigrationWriter as MigrationWriter
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
from django.apps import apps as apps
|
from django.apps import apps as apps
|
||||||
from django.core.management.base import BaseCommand as BaseCommand, CommandError as CommandError, no_translations as no_translations
|
from django.core.management.base import (
|
||||||
from django.core.management.sql import emit_post_migrate_signal as emit_post_migrate_signal, emit_pre_migrate_signal as emit_pre_migrate_signal
|
BaseCommand as BaseCommand,
|
||||||
|
CommandError as CommandError,
|
||||||
|
no_translations as no_translations,
|
||||||
|
)
|
||||||
|
from django.core.management.sql import (
|
||||||
|
emit_post_migrate_signal as emit_post_migrate_signal,
|
||||||
|
emit_pre_migrate_signal as emit_pre_migrate_signal,
|
||||||
|
)
|
||||||
from django.db import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS, connections as connections, router as router
|
from django.db import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS, connections as connections, router as router
|
||||||
from django.db.migrations.autodetector import MigrationAutodetector as MigrationAutodetector
|
from django.db.migrations.autodetector import MigrationAutodetector as MigrationAutodetector
|
||||||
from django.db.migrations.executor import MigrationExecutor as MigrationExecutor
|
from django.db.migrations.executor import MigrationExecutor as MigrationExecutor
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ from typing import Any, Dict, Optional, Sequence, Set, Tuple, Union
|
|||||||
from django.db.migrations.migration import Migration
|
from django.db.migrations.migration import Migration
|
||||||
from django.db.migrations.state import ProjectState
|
from django.db.migrations.state import ProjectState
|
||||||
|
|
||||||
from .exceptions import AmbiguityError as AmbiguityError, BadMigrationError as BadMigrationError, InconsistentMigrationHistory as InconsistentMigrationHistory, NodeNotFoundError as NodeNotFoundError
|
from .exceptions import (
|
||||||
|
AmbiguityError as AmbiguityError,
|
||||||
|
BadMigrationError as BadMigrationError,
|
||||||
|
InconsistentMigrationHistory as InconsistentMigrationHistory,
|
||||||
|
NodeNotFoundError as NodeNotFoundError,
|
||||||
|
)
|
||||||
|
|
||||||
MIGRATIONS_MODULE_NAME: str
|
MIGRATIONS_MODULE_NAME: str
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
from typing import Any, Generic, List, Optional, Type, TypeVar
|
from typing import Any, Generic, List, Optional, Tuple, Type, TypeVar
|
||||||
|
|
||||||
import django
|
import django
|
||||||
from django.contrib.admin import ModelAdmin
|
from django.contrib.admin import ModelAdmin
|
||||||
from django.contrib.admin.options import BaseModelAdmin
|
from django.contrib.admin.options import BaseModelAdmin
|
||||||
|
from django.db.models.manager import BaseManager
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
from django.views.generic.edit import FormMixin
|
from django.views.generic.edit import FormMixin
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
|
_VersionSpec = Tuple[int, int]
|
||||||
|
|
||||||
|
|
||||||
class MPGeneric(Generic[_T]):
|
class MPGeneric(Generic[_T]):
|
||||||
@@ -21,11 +24,15 @@ class MPGeneric(Generic[_T]):
|
|||||||
version: Optional[int]
|
version: Optional[int]
|
||||||
cls: Type[_T]
|
cls: Type[_T]
|
||||||
|
|
||||||
def __init__(self, cls: Type[_T], version: Optional[int] = None):
|
def __init__(self, cls: Type[_T], version: Optional[_VersionSpec] = None):
|
||||||
"""Set the data fields, basic constructor."""
|
"""Set the data fields, basic constructor."""
|
||||||
self.version = version
|
self.version = version
|
||||||
self.cls = cls
|
self.cls = cls
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
"""Better representation in tests and debug."""
|
||||||
|
return "<MPGeneric: {0}, versions={1}>".format(self.cls, self.version or "all")
|
||||||
|
|
||||||
|
|
||||||
# certain django classes need to be generic, but lack the __class_getitem__ dunder needed to
|
# 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
|
# annotate them: https://github.com/typeddjango/django-stubs/issues/507
|
||||||
@@ -34,13 +41,20 @@ _need_generic: List[MPGeneric[Any]] = [
|
|||||||
MPGeneric(ModelAdmin),
|
MPGeneric(ModelAdmin),
|
||||||
MPGeneric(FormMixin),
|
MPGeneric(FormMixin),
|
||||||
MPGeneric(BaseModelAdmin),
|
MPGeneric(BaseModelAdmin),
|
||||||
|
# These types do have native `__class_getitem__` method since django 3.1:
|
||||||
|
MPGeneric(QuerySet, (3, 1)),
|
||||||
|
MPGeneric(BaseManager, (3, 1)),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# currently just adds the __class_getitem__ dunder. if more monkeypatching is needed, add it here
|
# currently just adds the __class_getitem__ dunder. if more monkeypatching is needed, add it here
|
||||||
def monkeypatch() -> None:
|
def monkeypatch() -> None:
|
||||||
"""Monkey patch django as necessary to work properly with mypy."""
|
"""Monkey patch django as necessary to work properly with mypy."""
|
||||||
for el in filter(lambda x: django.VERSION[0] == x.version or x.version is None, _need_generic):
|
suited_for_this_version = filter(
|
||||||
|
spec.version is None or django.VERSION[:2] <= spec.version,
|
||||||
|
_need_generic,
|
||||||
|
)
|
||||||
|
for el in suited_for_this_version:
|
||||||
el.cls.__class_getitem__ = classmethod(lambda cls, *args, **kwargs: cls)
|
el.cls.__class_getitem__ = classmethod(lambda cls, *args, **kwargs: cls)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,49 @@
|
|||||||
import django_stubs_ext
|
import pytest
|
||||||
from django_stubs_ext.monkeypatch import _need_generic
|
|
||||||
|
|
||||||
|
import django_stubs_ext
|
||||||
|
from django_stubs_ext.monkeypatch import _need_generic, _VersionSpec, django
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def make_generic_classes(request, monkeypatch):
|
||||||
|
def fin():
|
||||||
|
for el in _need_generic:
|
||||||
|
delattr(el.cls, "__class_getitem__")
|
||||||
|
|
||||||
|
def factory(django_version=None):
|
||||||
|
if django_version is not None:
|
||||||
|
monkeypatch.setattr(django, "VERSION", django_version)
|
||||||
django_stubs_ext.monkeypatch()
|
django_stubs_ext.monkeypatch()
|
||||||
|
|
||||||
|
request.addfinalizer(fin)
|
||||||
|
return factory
|
||||||
|
|
||||||
def test_patched_generics() -> None:
|
|
||||||
|
def test_patched_generics(make_generic_classes) -> None:
|
||||||
"""Test that the generics actually get patched."""
|
"""Test that the generics actually get patched."""
|
||||||
|
make_generic_classes()
|
||||||
|
|
||||||
for el in _need_generic:
|
for el in _need_generic:
|
||||||
# This only throws an exception if the monkeypatch failed
|
if el.version is None:
|
||||||
assert el.cls[type] == el.cls # `type` is arbitrary
|
assert el.cls[type] is el.cls # `type` is arbitrary
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"django_version",
|
||||||
|
[
|
||||||
|
(2, 2),
|
||||||
|
(3, 0),
|
||||||
|
(3, 1),
|
||||||
|
(3, 2),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_patched_version_specific(
|
||||||
|
django_version: _VersionSpec,
|
||||||
|
make_generic_classes,
|
||||||
|
) -> None:
|
||||||
|
"""Test version speicific types."""
|
||||||
|
make_generic_classes(django_version)
|
||||||
|
|
||||||
|
for el in _need_generic:
|
||||||
|
if el.version is not None and el.version[:2] <= django_version:
|
||||||
|
assert el.cls[int] is el.cls
|
||||||
|
|||||||
Reference in New Issue
Block a user