mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-10 22:11:54 +08:00
Compare commits
27 Commits
v1.3.1
...
new-readme
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b846c0f2ed | ||
|
|
7af2adb665 | ||
|
|
0052a83968 | ||
|
|
5b3088a17a | ||
|
|
f89a0fbbaa | ||
|
|
438f8b5829 | ||
|
|
836d5acd8f | ||
|
|
bfae51e64c | ||
|
|
e3801918e3 | ||
|
|
6f296b0a91 | ||
|
|
7ba578f6b2 | ||
|
|
cb123de105 | ||
|
|
38135f2d1f | ||
|
|
998b659749 | ||
|
|
72f69e1c5e | ||
|
|
d666ecd36f | ||
|
|
c1af26c027 | ||
|
|
3c3dfcbc9f | ||
|
|
1196336e3b | ||
|
|
665f4d8ea1 | ||
|
|
b3ed9e4827 | ||
|
|
fb1593630a | ||
|
|
031d42a75d | ||
|
|
f7e1cfc6c7 | ||
|
|
d0c25e3bce | ||
|
|
1c31e71ffc | ||
|
|
6b3b6be3c1 |
101
README.md
101
README.md
@@ -1,14 +1,12 @@
|
||||
<img src="http://mypy-lang.org/static/mypy_light.svg" alt="mypy logo" width="300px"/>
|
||||
|
||||
# pep484 stubs for Django framework
|
||||
# Typesafe Django Framework
|
||||
|
||||
[](https://travis-ci.com/typeddjango/django-stubs)
|
||||
[](http://mypy-lang.org/)
|
||||
[](https://gitter.im/mypy-django/Lobby)
|
||||
|
||||
This package contains type stubs and mypy plugin to provide more precise static types and type inference for Django framework. Django uses some Python "magic" that makes having precise types for some code patterns problematic. This is why we need to accompany the stubs with mypy plugins. The final goal is to be able to get precise types for most common patterns.
|
||||
|
||||
Could be run on earlier versions of Django, but expect some missing imports warnings.
|
||||
This package contains [type stubs](https://www.python.org/dev/peps/pep-0561/) and a custom mypy plugin to provide more precise static types and type inference for Django framework. Django uses some Python "magic" that makes having precise types for some code patterns problematic. This is why we need this project. The final goal is to be able to get precise types for most common patterns.
|
||||
|
||||
|
||||
## Installation
|
||||
@@ -17,8 +15,35 @@ Could be run on earlier versions of Django, but expect some missing imports warn
|
||||
pip install django-stubs
|
||||
```
|
||||
|
||||
See [Configutation](#configuration) section to get started.
|
||||
|
||||
## Mypy compatibility
|
||||
|
||||
## Configuration
|
||||
|
||||
To make `mypy` happy, you will need to add:
|
||||
|
||||
```ini
|
||||
[mypy]
|
||||
plugins =
|
||||
mypy_django_plugin.main
|
||||
|
||||
[mypy.plugins.django-stubs]
|
||||
django_settings_module = "myproject.settings"
|
||||
```
|
||||
|
||||
in your `mypy.ini` or `setup.cfg` [file](https://mypy.readthedocs.io/en/latest/config_file.html).
|
||||
|
||||
Two things happeining here:
|
||||
|
||||
1. We need to explicitly list our plugin to be loaded by `mypy`
|
||||
2. Our plugin also requires `django` settings module (what you put into `DJANGO_SETTINGS_MODULE` variable) to be specified
|
||||
|
||||
This fully working [typed boilerplate](https://github.com/wemake-services/wemake-django-template) can serve you as an example.
|
||||
|
||||
|
||||
## Version compatibility
|
||||
|
||||
We rely on different `django` and `mypy` versions:
|
||||
|
||||
| django-stubs | mypy version | django version | python version
|
||||
| ------------ | ---- | ---- | ---- |
|
||||
@@ -28,58 +53,66 @@ pip install django-stubs
|
||||
| 0.12.x | old semantic analyzer (<0.711), dmypy support | 2.1.x | ^3.6
|
||||
|
||||
|
||||
## Configuration
|
||||
## FAQ
|
||||
|
||||
To make mypy aware of the plugin, you need to add
|
||||
> Is this an official Django project?
|
||||
|
||||
```ini
|
||||
[mypy]
|
||||
plugins =
|
||||
mypy_django_plugin.main
|
||||
```
|
||||
No, it is not. We are indendepent from Django at the moment.
|
||||
There's a [proposal](https://github.com/django/deps/pull/65) to merge our project into the Django itself.
|
||||
You show your support by linking the PR.
|
||||
|
||||
in your `mypy.ini` or `setup.cfg` [file](https://mypy.readthedocs.io/en/latest/config_file.html).
|
||||
> Is it safe to use this in production?
|
||||
|
||||
Plugin also requires Django settings module (what you put into `DJANGO_SETTINGS_MODULE` variable) to be specified.
|
||||
Yes, it is! This project does not affect your runtime at all.
|
||||
It only affects `mypy` type checking process.
|
||||
|
||||
```ini
|
||||
[mypy]
|
||||
strict_optional = True
|
||||
But, it does not make sense to use this project without `mypy`.
|
||||
|
||||
# This one is new:
|
||||
[mypy.plugins.django-stubs]
|
||||
django_settings_module = mysettings
|
||||
```
|
||||
|
||||
Where `mysettings` is a value of `DJANGO_SETTINGS_MODULE` (with or without quotes)
|
||||
> mypy crashes when I run it with this plugin installed
|
||||
|
||||
Current implementation uses Django runtime to extract models information, so it will crash, if your installed apps `models.py` is not correct. For this same reason, you cannot use `reveal_type` inside global scope of any Python file that will be executed for `django.setup()`.
|
||||
|
||||
In other words, if your `manage.py runserver` crashes, mypy will crash too.
|
||||
You can also run `mypy` with [`--tb`](https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-show-traceback)
|
||||
option to get extra information about the error.
|
||||
|
||||
This fully working [typed boilerplate](https://github.com/wemake-services/wemake-django-template) can serve you as an example.
|
||||
> I cannot use QuerySet or Manager with type annotations
|
||||
|
||||
You can get a `TypeError: 'type' object is not subscriptable`
|
||||
when you will try to use `QuerySet[MyModel]` or `Manager[MyModel]`.
|
||||
|
||||
## Notes
|
||||
This happens because Django classes do not support [`__class_getitem__`](https://www.python.org/dev/peps/pep-0560/#class-getitem) magic method.
|
||||
|
||||
Type implementation monkey-patches Django to add `__class_getitem__` to the `Manager` class.
|
||||
If you would use Python3.7 and do that too in your code, you can make things like
|
||||
There are several things 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.
|
||||
|
||||
> How can I use HttpRequest with custom user model?
|
||||
|
||||
You can subclass standard request like so:
|
||||
|
||||
```python
|
||||
class MyUserManager(models.Manager['MyUser']):
|
||||
pass
|
||||
from django.http import HttpRequest
|
||||
from my_user_app.models import MyUser
|
||||
|
||||
class MyUser(models.Model):
|
||||
objects = MyUserManager()
|
||||
class MyRequest(HttpRequest):
|
||||
user: MyUser
|
||||
```
|
||||
|
||||
work, which should make a error messages a bit better.
|
||||
And then use `MyRequest` instead of standard `HttpRequest` inside your project.
|
||||
|
||||
|
||||
## Related projects
|
||||
|
||||
- [`awesome-python-typing`](https://github.com/typeddjango/awesome-python-typing) - Awesome list of all typing-related things in Python.
|
||||
- [`djangorestframework-stubs`](https://github.com/typeddjango/djangorestframework-stubs) - Stubs for Django REST Framework.
|
||||
- [`pytest-mypy-plugins`](https://github.com/typeddjango/pytest-mypy-plugins) - `pytest` plugin that we use for testing `mypy` stubs and plugins.
|
||||
- [`wemake-django-template`](https://github.com/wemake-services/wemake-django-template) - Create new typed Django projects in seconds.
|
||||
|
||||
Otherwise, custom type will be created in mypy, named `MyUser__MyUserManager`, which will rewrite base manager as `models.Manager[User]` to make methods like `get_queryset()` and others return properly typed `QuerySet`.
|
||||
|
||||
|
||||
## To get help
|
||||
|
||||
We have Gitter here: <https://gitter.im/mypy-django/Lobby>
|
||||
|
||||
If you think you have more generic typing issue, please refer to https://github.com/python/mypy and their Gitter.
|
||||
If you think you have more generic typing issue, please refer to <https://github.com/python/mypy> and their Gitter.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
black
|
||||
pytest-mypy-plugins==1.1.0
|
||||
pytest-mypy-plugins==1.2.0
|
||||
psycopg2
|
||||
flake8==3.7.9
|
||||
flake8-pyi==19.3.0
|
||||
|
||||
@@ -5,10 +5,10 @@ from django.http.response import HttpResponse, HttpResponseBase
|
||||
|
||||
from django.urls import URLResolver, URLPattern
|
||||
|
||||
handler400: Callable[..., HttpResponse] = ...
|
||||
handler403: Callable[..., HttpResponse] = ...
|
||||
handler404: Callable[..., HttpResponse] = ...
|
||||
handler500: Callable[..., HttpResponse] = ...
|
||||
handler400: Union[str, Callable[..., HttpResponse]] = ...
|
||||
handler403: Union[str, Callable[..., HttpResponse]] = ...
|
||||
handler404: Union[str, Callable[..., HttpResponse]] = ...
|
||||
handler500: Union[str, Callable[..., HttpResponse]] = ...
|
||||
|
||||
IncludedURLConf = Tuple[List[URLResolver], Optional[str], Optional[str]]
|
||||
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
from .messages import Warning as Warning, Info as Info, Debug as Debug, Error as Error, Critical as Critical
|
||||
from .messages import (
|
||||
CheckMessage as CheckMessage,
|
||||
Debug as Debug,
|
||||
Info as Info,
|
||||
Warning as Warning,
|
||||
Error as Error,
|
||||
Critical as Critical,
|
||||
DEBUG as DEBUG,
|
||||
INFO as INFO,
|
||||
WARNING as WARNING,
|
||||
ERROR as ERROR,
|
||||
CRITICAL as CRITICAL,
|
||||
)
|
||||
|
||||
from .registry import run_checks as run_checks, Tags as Tags, register as register
|
||||
from .registry import register as register, run_checks as run_checks, tag_exists as tag_exists, Tags as Tags
|
||||
|
||||
from . import model_checks as model_checks
|
||||
|
||||
@@ -7,11 +7,11 @@ ERROR: int
|
||||
CRITICAL: int
|
||||
|
||||
class CheckMessage:
|
||||
level: Any = ...
|
||||
msg: Any = ...
|
||||
hint: Any = ...
|
||||
level: int = ...
|
||||
msg: str = ...
|
||||
hint: Optional[str] = ...
|
||||
obj: Any = ...
|
||||
id: Any = ...
|
||||
id: Optional[str] = ...
|
||||
def __init__(
|
||||
self, level: int, msg: str, hint: Optional[str] = ..., obj: Any = ..., id: Optional[str] = ...
|
||||
) -> None: ...
|
||||
@@ -25,19 +25,9 @@ class Info(CheckMessage):
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
||||
|
||||
class Warning(CheckMessage):
|
||||
hint: str
|
||||
id: str
|
||||
level: int
|
||||
msg: str
|
||||
obj: Any
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
||||
|
||||
class Error(CheckMessage):
|
||||
hint: None
|
||||
id: str
|
||||
level: int
|
||||
msg: str
|
||||
obj: Any
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
||||
|
||||
class Critical(CheckMessage):
|
||||
|
||||
@@ -8,7 +8,7 @@ _T = TypeVar("_T", bound="File")
|
||||
|
||||
class File(FileProxyMixin, IO[Any]):
|
||||
DEFAULT_CHUNK_SIZE: Any = ...
|
||||
file: StringIO = ...
|
||||
file: IO[Any] = ...
|
||||
name: str = ...
|
||||
mode: str = ...
|
||||
def __init__(self, file: Any, name: Optional[str] = ...) -> None: ...
|
||||
|
||||
@@ -23,13 +23,16 @@ class OutputWrapper(TextIOBase):
|
||||
@property
|
||||
def style_func(self): ...
|
||||
@style_func.setter
|
||||
def style_func(self, style_func: Any): ...
|
||||
def style_func(self, style_func: Callable[[str], str]): ...
|
||||
ending: str = ...
|
||||
def __init__(
|
||||
self, out: Union[StringIO, TextIOWrapper], style_func: Optional[Callable] = ..., ending: str = ...
|
||||
self, out: Union[StringIO, TextIOWrapper], style_func: Optional[Callable[[str], str]] = ..., ending: str = ...
|
||||
) -> None: ...
|
||||
def __getattr__(self, name: str) -> Callable: ...
|
||||
def isatty(self) -> bool: ...
|
||||
def write( # type: ignore[override]
|
||||
self, msg: str, style_func: Optional[Callable[[str], str]] = ..., ending: Optional[str] = ...
|
||||
) -> None: ...
|
||||
|
||||
class BaseCommand:
|
||||
help: str = ...
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
def supports_color() -> bool: ...
|
||||
|
||||
class Style:
|
||||
def DEBUG(self, text: str) -> str: ...
|
||||
def INFO(self, text: str) -> str: ...
|
||||
def ERROR(self, text: str) -> str: ...
|
||||
def SUCCESS(self, text: str) -> str: ...
|
||||
def WARNING(self, text: str) -> str: ...
|
||||
def ERROR(self, text: str) -> str: ...
|
||||
def NOTICE(self, text: str) -> str: ...
|
||||
def SQL_FIELD(self, text: str) -> str: ...
|
||||
def SQL_COLTYPE(self, text: str) -> str: ...
|
||||
def SQL_KEYWORD(self, text: str) -> str: ...
|
||||
def SQL_TABLE(self, text: str) -> str: ...
|
||||
def HTTP_INFO(self, text: str) -> str: ...
|
||||
def HTTP_SUCCESS(self, text: str) -> str: ...
|
||||
def HTTP_REDIRECT(self, text: str) -> str: ...
|
||||
def HTTP_NOT_MODIFIED(self, text: str) -> str: ...
|
||||
def HTTP_BAD_REQUEST(self, text: str) -> str: ...
|
||||
def HTTP_NOT_FOUND(self, text: str) -> str: ...
|
||||
def HTTP_SERVER_ERROR(self, text: str) -> str: ...
|
||||
def MIGRATE_HEADING(self, text: str) -> str: ...
|
||||
def MIGRATE_LABEL(self, text: str) -> str: ...
|
||||
def ERROR_OUTPUT(self, text: str) -> str: ...
|
||||
|
||||
def make_style(config_string: str = ...) -> Style: ...
|
||||
def no_style() -> Style: ...
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from decimal import Decimal
|
||||
from re import RegexFlag
|
||||
from typing import Any, Dict, List, Optional, Union, Pattern, Collection, Callable, Tuple
|
||||
from typing import Any, Callable, Collection, Dict, List, Optional, Pattern, Tuple, Union
|
||||
|
||||
from django.core.files.base import File
|
||||
|
||||
@@ -13,7 +13,7 @@ def _lazy_re_compile(regex: _Regex, flags: int = ...): ...
|
||||
|
||||
class RegexValidator:
|
||||
regex: _Regex = ...
|
||||
message: Any = ...
|
||||
message: str = ...
|
||||
code: str = ...
|
||||
inverse_match: bool = ...
|
||||
flags: int = ...
|
||||
@@ -31,24 +31,24 @@ class URLValidator(RegexValidator):
|
||||
ul: str = ...
|
||||
ipv4_re: str = ...
|
||||
ipv6_re: str = ...
|
||||
hostname_re: Any = ...
|
||||
domain_re: Any = ...
|
||||
tld_re: Any = ...
|
||||
host_re: Any = ...
|
||||
schemes: Any = ...
|
||||
hostname_re: str = ...
|
||||
domain_re: str = ...
|
||||
tld_re: str = ...
|
||||
host_re: str = ...
|
||||
schemes: List[str] = ...
|
||||
def __init__(self, schemes: Optional[Collection[str]] = ..., **kwargs: Any) -> None: ...
|
||||
|
||||
integer_validator: Any
|
||||
integer_validator: RegexValidator = ...
|
||||
|
||||
def validate_integer(value: Optional[Union[float, str]]) -> None: ...
|
||||
|
||||
class EmailValidator:
|
||||
message: str = ...
|
||||
code: str = ...
|
||||
user_regex: Any = ...
|
||||
domain_regex: Any = ...
|
||||
literal_regex: Any = ...
|
||||
domain_whitelist: Any = ...
|
||||
user_regex: Pattern = ...
|
||||
domain_regex: Pattern = ...
|
||||
literal_regex: Pattern = ...
|
||||
domain_whitelist: List[str] = ...
|
||||
def __init__(
|
||||
self,
|
||||
message: Optional[_ErrorMessage] = ...,
|
||||
@@ -68,9 +68,10 @@ def validate_ipv4_address(value: str) -> None: ...
|
||||
def validate_ipv6_address(value: str) -> None: ...
|
||||
def validate_ipv46_address(value: str) -> None: ...
|
||||
|
||||
ip_address_validator_map: Dict[str, Tuple[Callable[[Any], None], str]]
|
||||
_IPValidator = Tuple[Callable[[Any], None], str]
|
||||
ip_address_validator_map: Dict[str, _IPValidator]
|
||||
|
||||
def ip_address_validators(protocol: str, unpack_ipv4: bool) -> Any: ...
|
||||
def ip_address_validators(protocol: str, unpack_ipv4: bool) -> _IPValidator: ...
|
||||
def int_list_validator(
|
||||
sep: str = ..., message: Optional[_ErrorMessage] = ..., code: str = ..., allow_negative: bool = ...
|
||||
) -> RegexValidator: ...
|
||||
@@ -92,7 +93,7 @@ class MinLengthValidator(BaseValidator): ...
|
||||
class MaxLengthValidator(BaseValidator): ...
|
||||
|
||||
class DecimalValidator:
|
||||
messages: Any = ...
|
||||
messages: Dict[str, str] = ...
|
||||
max_digits: int = ...
|
||||
decimal_places: int = ...
|
||||
def __init__(self, max_digits: Optional[Union[int, str]], decimal_places: Optional[Union[int, str]]) -> None: ...
|
||||
|
||||
@@ -2,7 +2,7 @@ from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Ty
|
||||
|
||||
from django.core.checks.messages import CheckMessage
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models.manager import Manager
|
||||
from django.db.models.manager import BaseManager
|
||||
from django.db.models.options import Options
|
||||
|
||||
_Self = TypeVar("_Self", bound="Model")
|
||||
@@ -13,9 +13,9 @@ class Model(metaclass=ModelBase):
|
||||
class DoesNotExist(Exception): ...
|
||||
class MultipleObjectsReturned(Exception): ...
|
||||
class Meta: ...
|
||||
_default_manager: Manager[Model]
|
||||
_meta: Options[Any]
|
||||
objects: Manager[Any]
|
||||
_default_manager: BaseManager[Model]
|
||||
objects: BaseManager[Any]
|
||||
pk: Any = ...
|
||||
def __init__(self: _Self, *args, **kwargs) -> None: ...
|
||||
def delete(self, using: Any = ..., keep_parents: bool = ...) -> Tuple[int, Dict[str, int]]: ...
|
||||
|
||||
@@ -13,6 +13,7 @@ class BaseManager(QuerySet[_T]):
|
||||
name: str = ...
|
||||
model: Type[_T] = ...
|
||||
db: str
|
||||
_db: Optional[str]
|
||||
def __init__(self) -> None: ...
|
||||
def deconstruct(self) -> Tuple[bool, str, None, Tuple, Dict[str, int]]: ...
|
||||
def check(self, **kwargs: Any) -> List[Any]: ...
|
||||
|
||||
@@ -31,6 +31,7 @@ _T = TypeVar("_T", bound=models.Model, covariant=True)
|
||||
_QS = TypeVar("_QS", bound="_BaseQuerySet")
|
||||
|
||||
class _BaseQuerySet(Generic[_T], Sized):
|
||||
model: Type[_T]
|
||||
query: Query
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@@ -205,10 +205,9 @@ class CallableChoiceIterator:
|
||||
def __iter__(self) -> None: ...
|
||||
|
||||
class ChoiceField(Field):
|
||||
choices: Any = ...
|
||||
def __init__(
|
||||
self,
|
||||
choices: Any = ...,
|
||||
choices: Union[_FieldChoices, Callable[[], _FieldChoices]] = ...,
|
||||
required: bool = ...,
|
||||
widget: Optional[Union[Widget, Type[Widget]]] = ...,
|
||||
label: Optional[Any] = ...,
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
from datetime import datetime
|
||||
from typing import Any, Callable, Dict, Iterator, List, Mapping, MutableMapping, Optional, Sequence, Tuple, Type, Union
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterator,
|
||||
List,
|
||||
Mapping,
|
||||
MutableMapping,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
ClassVar,
|
||||
Container,
|
||||
TypeVar,
|
||||
)
|
||||
from unittest.mock import MagicMock
|
||||
from uuid import UUID
|
||||
|
||||
@@ -24,6 +40,11 @@ _Fields = Union[List[Union[Callable, str]], Sequence[str], Literal["__all__"]]
|
||||
_Labels = Dict[str, str]
|
||||
_ErrorMessages = Dict[str, Dict[str, str]]
|
||||
|
||||
_M = TypeVar("_M", bound=Model)
|
||||
|
||||
def construct_instance(
|
||||
form: BaseForm, instance: _M, fields: Optional[Container[str]] = ..., exclude: Optional[Container[str]] = ...
|
||||
) -> _M: ...
|
||||
def model_to_dict(
|
||||
instance: Model, fields: Optional[_Fields] = ..., exclude: Optional[_Fields] = ...
|
||||
) -> Dict[str, Any]: ...
|
||||
@@ -76,7 +97,8 @@ class BaseModelForm(BaseForm):
|
||||
save_m2m: Any = ...
|
||||
def save(self, commit: bool = ...) -> Any: ...
|
||||
|
||||
class ModelForm(BaseModelForm): ...
|
||||
class ModelForm(BaseModelForm, metaclass=ModelFormMetaclass):
|
||||
base_fields: ClassVar[Dict[str, Field]] = ...
|
||||
|
||||
def modelform_factory(
|
||||
model: Type[Model],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from typing import Any, Optional, Tuple
|
||||
|
||||
from django.core.cache.backends.base import BaseCache
|
||||
from django.core.cache.backends.locmem import LocMemCache
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
from django.http.response import HttpResponse, HttpResponseBase
|
||||
|
||||
@@ -28,5 +27,5 @@ def learn_cache_key(
|
||||
response: HttpResponse,
|
||||
cache_timeout: Optional[float] = ...,
|
||||
key_prefix: Optional[str] = ...,
|
||||
cache: Optional[LocMemCache] = ...,
|
||||
cache: Optional[BaseCache] = ...,
|
||||
) -> str: ...
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union, TypeVar, Generic, overload
|
||||
from functools import wraps as wraps # noqa: F401
|
||||
|
||||
from django.db.models.base import Model
|
||||
|
||||
def curry(_curried_func: Any, *args: Any, **kwargs: Any): ...
|
||||
|
||||
class cached_property:
|
||||
func: Callable = ...
|
||||
_T = TypeVar("_T")
|
||||
|
||||
class cached_property(Generic[_T]):
|
||||
func: Callable[..., _T] = ...
|
||||
__doc__: Any = ...
|
||||
name: str = ...
|
||||
def __init__(self, func: Callable, name: Optional[str] = ...) -> None: ...
|
||||
def __get__(self, instance: Any, cls: Type[Any] = ...) -> Any: ...
|
||||
def __init__(self, func: Callable[..., _T], name: Optional[str] = ...): ...
|
||||
@overload
|
||||
def __get__(self, instance: None, cls: Type[Any] = ...) -> "cached_property[_T]": ...
|
||||
@overload
|
||||
def __get__(self, instance: object, cls: Type[Any] = ...) -> _T: ...
|
||||
|
||||
class Promise: ...
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from typing import Any, Callable, Dict, Optional, Sequence, Type, Union
|
||||
|
||||
from django.forms.forms import BaseForm
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
|
||||
from django.views.generic.detail import BaseDetailView, SingleObjectMixin, SingleObjectTemplateResponseMixin
|
||||
from typing_extensions import Literal
|
||||
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
|
||||
class FormMixin(ContextMixin):
|
||||
initial: Dict[str, Any] = ...
|
||||
form_class: Optional[Type[BaseForm]] = ...
|
||||
|
||||
@@ -23,9 +23,7 @@ FORM_MIXIN_CLASS_FULLNAME = 'django.views.generic.edit.FormMixin'
|
||||
|
||||
MANAGER_CLASSES = {
|
||||
MANAGER_CLASS_FULLNAME,
|
||||
RELATED_MANAGER_CLASS,
|
||||
BASE_MANAGER_CLASS_FULLNAME,
|
||||
# QUERYSET_CLASS_FULLNAME
|
||||
}
|
||||
|
||||
RELATED_FIELDS_CLASSES = {
|
||||
|
||||
@@ -10,8 +10,8 @@ from mypy import checker
|
||||
from mypy.checker import TypeChecker
|
||||
from mypy.mro import calculate_mro
|
||||
from mypy.nodes import (
|
||||
GDEF, MDEF, Argument, Block, ClassDef, Expression, FuncDef, MemberExpr, MypyFile, NameExpr, StrExpr, SymbolNode,
|
||||
SymbolTable, SymbolTableNode, TypeInfo, Var,
|
||||
GDEF, MDEF, Argument, Block, ClassDef, Expression, FuncDef, MemberExpr, MypyFile, NameExpr, PlaceholderNode,
|
||||
StrExpr, SymbolNode, SymbolTable, SymbolTableNode, TypeInfo, Var,
|
||||
)
|
||||
from mypy.plugin import (
|
||||
AttributeContext, CheckerPluginInterface, ClassDefContext, DynamicClassDefContext, FunctionContext, MethodContext,
|
||||
@@ -309,26 +309,67 @@ def add_new_sym_for_info(info: TypeInfo, *, name: str, sym_type: MypyType) -> No
|
||||
plugin_generated=True)
|
||||
|
||||
|
||||
def _prepare_new_method_arguments(node: FuncDef) -> Tuple[List[Argument], MypyType]:
|
||||
arguments = []
|
||||
for argument in node.arguments[1:]:
|
||||
if argument.type_annotation is None:
|
||||
argument.type_annotation = AnyType(TypeOfAny.unannotated)
|
||||
arguments.append(argument)
|
||||
|
||||
if isinstance(node.type, CallableType):
|
||||
return_type = node.type.ret_type
|
||||
else:
|
||||
return_type = AnyType(TypeOfAny.unannotated)
|
||||
|
||||
return arguments, return_type
|
||||
def build_unannotated_method_args(method_node: FuncDef) -> Tuple[List[Argument], MypyType]:
|
||||
prepared_arguments = []
|
||||
for argument in method_node.arguments[1:]:
|
||||
argument.type_annotation = AnyType(TypeOfAny.unannotated)
|
||||
prepared_arguments.append(argument)
|
||||
return_type = AnyType(TypeOfAny.unannotated)
|
||||
return prepared_arguments, return_type
|
||||
|
||||
|
||||
def copy_method_to_another_class(ctx: ClassDefContext, self_type: Instance,
|
||||
new_method_name: str, method_node: FuncDef) -> None:
|
||||
arguments, return_type = _prepare_new_method_arguments(method_node)
|
||||
semanal_api = get_semanal_api(ctx)
|
||||
if method_node.type is None:
|
||||
if not semanal_api.final_iteration:
|
||||
semanal_api.defer()
|
||||
return
|
||||
|
||||
arguments, return_type = build_unannotated_method_args(method_node)
|
||||
add_method(ctx,
|
||||
new_method_name,
|
||||
args=arguments,
|
||||
return_type=return_type,
|
||||
self_type=self_type)
|
||||
return
|
||||
|
||||
method_type = method_node.type
|
||||
if not isinstance(method_type, CallableType):
|
||||
if not semanal_api.final_iteration:
|
||||
semanal_api.defer()
|
||||
return
|
||||
|
||||
arguments = []
|
||||
bound_return_type = semanal_api.anal_type(method_type.ret_type,
|
||||
allow_placeholder=True)
|
||||
assert bound_return_type is not None
|
||||
|
||||
if isinstance(bound_return_type, PlaceholderNode):
|
||||
return
|
||||
|
||||
for arg_name, arg_type, original_argument in zip(method_type.arg_names[1:],
|
||||
method_type.arg_types[1:],
|
||||
method_node.arguments[1:]):
|
||||
bound_arg_type = semanal_api.anal_type(arg_type, allow_placeholder=True)
|
||||
assert bound_arg_type is not None
|
||||
|
||||
if isinstance(bound_arg_type, PlaceholderNode):
|
||||
return
|
||||
|
||||
var = Var(name=original_argument.variable.name,
|
||||
type=arg_type)
|
||||
var.line = original_argument.variable.line
|
||||
var.column = original_argument.variable.column
|
||||
argument = Argument(variable=var,
|
||||
type_annotation=bound_arg_type,
|
||||
initializer=original_argument.initializer,
|
||||
kind=original_argument.kind)
|
||||
argument.set_line(original_argument)
|
||||
arguments.append(argument)
|
||||
|
||||
add_method(ctx,
|
||||
new_method_name,
|
||||
args=arguments,
|
||||
return_type=return_type,
|
||||
return_type=bound_return_type,
|
||||
self_type=self_type)
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
from mypy.nodes import (
|
||||
GDEF, FuncDef, MemberExpr, NameExpr, StrExpr, SymbolTableNode, TypeInfo,
|
||||
GDEF, FuncDef, MemberExpr, NameExpr, RefExpr, StrExpr, SymbolTableNode, TypeInfo,
|
||||
)
|
||||
from mypy.plugin import ClassDefContext, DynamicClassDefContext
|
||||
from mypy.types import AnyType, Instance, TypeOfAny
|
||||
|
||||
from mypy_django_plugin.lib import helpers
|
||||
from mypy_django_plugin.lib import fullnames, helpers
|
||||
|
||||
|
||||
def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefContext) -> None:
|
||||
semanal_api = helpers.get_semanal_api(ctx)
|
||||
|
||||
assert isinstance(ctx.call.callee, MemberExpr)
|
||||
assert isinstance(ctx.call.callee.expr, NameExpr)
|
||||
base_manager_info = ctx.call.callee.expr.node
|
||||
callee = ctx.call.callee
|
||||
assert isinstance(callee, MemberExpr)
|
||||
assert isinstance(callee.expr, RefExpr)
|
||||
|
||||
base_manager_info = callee.expr.node
|
||||
if base_manager_info is None:
|
||||
if not semanal_api.final_iteration:
|
||||
semanal_api.defer()
|
||||
@@ -63,9 +65,13 @@ def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefConte
|
||||
class_def_context = ClassDefContext(cls=new_manager_info.defn,
|
||||
reason=ctx.call, api=semanal_api)
|
||||
self_type = Instance(new_manager_info, [])
|
||||
for name, sym in derived_queryset_info.names.items():
|
||||
if isinstance(sym.node, FuncDef):
|
||||
helpers.copy_method_to_another_class(class_def_context,
|
||||
self_type,
|
||||
new_method_name=name,
|
||||
method_node=sym.node)
|
||||
# we need to copy all methods in MRO before django.db.models.query.QuerySet
|
||||
for class_mro_info in derived_queryset_info.mro:
|
||||
if class_mro_info.fullname == fullnames.QUERYSET_CLASS_FULLNAME:
|
||||
break
|
||||
for name, sym in class_mro_info.names.items():
|
||||
if isinstance(sym.node, FuncDef):
|
||||
helpers.copy_method_to_another_class(class_def_context,
|
||||
self_type,
|
||||
new_method_name=name,
|
||||
method_node=sym.node)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Dict, Optional, Type, cast
|
||||
from typing import Dict, List, Optional, Type, cast
|
||||
|
||||
from django.db.models.base import Model
|
||||
from django.db.models.fields import DateField, DateTimeField
|
||||
@@ -58,6 +58,12 @@ class ModelClassInitializer:
|
||||
name=name,
|
||||
sym_type=typ)
|
||||
|
||||
def add_new_class_for_current_module(self, name: str, bases: List[Instance]) -> TypeInfo:
|
||||
current_module = self.api.modules[self.model_classdef.info.module_name]
|
||||
new_class_info = helpers.add_new_class_for_module(current_module,
|
||||
name=name, bases=bases)
|
||||
return new_class_info
|
||||
|
||||
def run(self) -> None:
|
||||
model_cls = self.django_context.get_model_class_by_fullname(self.model_classdef.fullname)
|
||||
if model_cls is None:
|
||||
@@ -144,7 +150,7 @@ class AddManagers(ModelClassInitializer):
|
||||
return False
|
||||
|
||||
def is_any_parametrized_manager(self, typ: Instance) -> bool:
|
||||
return typ.type.fullname == fullnames.MANAGER_CLASS_FULLNAME and isinstance(typ.args[0], AnyType)
|
||||
return typ.type.fullname in fullnames.MANAGER_CLASSES and isinstance(typ.args[0], AnyType)
|
||||
|
||||
def get_generated_manager_mappings(self, base_manager_fullname: str) -> Dict[str, str]:
|
||||
base_manager_info = self.lookup_typeinfo(base_manager_fullname)
|
||||
@@ -164,14 +170,12 @@ class AddManagers(ModelClassInitializer):
|
||||
[Instance(self.model_classdef.info, [])])
|
||||
bases.append(original_base)
|
||||
|
||||
current_module = self.api.modules[self.model_classdef.info.module_name]
|
||||
custom_manager_info = helpers.add_new_class_for_module(current_module,
|
||||
name=name, bases=bases)
|
||||
new_manager_info = self.add_new_class_for_current_module(name, bases)
|
||||
# copy fields to a new manager
|
||||
new_cls_def_context = ClassDefContext(cls=custom_manager_info.defn,
|
||||
new_cls_def_context = ClassDefContext(cls=new_manager_info.defn,
|
||||
reason=self.ctx.reason,
|
||||
api=self.api)
|
||||
custom_manager_type = Instance(custom_manager_info, [Instance(self.model_classdef.info, [])])
|
||||
custom_manager_type = Instance(new_manager_info, [Instance(self.model_classdef.info, [])])
|
||||
|
||||
for name, sym in base_manager_info.names.items():
|
||||
# replace self type with new class, if copying method
|
||||
@@ -185,10 +189,10 @@ class AddManagers(ModelClassInitializer):
|
||||
new_sym = sym.copy()
|
||||
if isinstance(new_sym.node, Var):
|
||||
new_var = Var(name, type=sym.type)
|
||||
new_var.info = custom_manager_info
|
||||
new_var._fullname = custom_manager_info.fullname + '.' + name
|
||||
new_var.info = new_manager_info
|
||||
new_var._fullname = new_manager_info.fullname + '.' + name
|
||||
new_sym.node = new_var
|
||||
custom_manager_info.names[name] = new_sym
|
||||
new_manager_info.names[name] = new_sym
|
||||
|
||||
return custom_manager_type
|
||||
|
||||
@@ -268,15 +272,30 @@ class AddRelatedManagers(ModelClassInitializer):
|
||||
|
||||
if isinstance(relation, (ManyToOneRel, ManyToManyRel)):
|
||||
try:
|
||||
manager_info = self.lookup_typeinfo_or_incomplete_defn_error(fullnames.RELATED_MANAGER_CLASS)
|
||||
related_manager_info = self.lookup_typeinfo_or_incomplete_defn_error(fullnames.RELATED_MANAGER_CLASS) # noqa: E501
|
||||
if 'objects' not in related_model_info.names:
|
||||
raise helpers.IncompleteDefnException()
|
||||
except helpers.IncompleteDefnException as exc:
|
||||
if not self.api.final_iteration:
|
||||
raise exc
|
||||
else:
|
||||
continue
|
||||
self.add_new_node_to_model_class(attname,
|
||||
Instance(manager_info, [Instance(related_model_info, [])]))
|
||||
continue
|
||||
|
||||
# create new RelatedManager subclass
|
||||
parametrized_related_manager_type = Instance(related_manager_info,
|
||||
[Instance(related_model_info, [])])
|
||||
default_manager_type = related_model_info.names['objects'].type
|
||||
if (default_manager_type is None
|
||||
or not isinstance(default_manager_type, Instance)
|
||||
or default_manager_type.type.fullname == fullnames.MANAGER_CLASS_FULLNAME):
|
||||
self.add_new_node_to_model_class(attname, parametrized_related_manager_type)
|
||||
continue
|
||||
|
||||
name = related_model_cls.__name__ + '_' + 'RelatedManager'
|
||||
bases = [parametrized_related_manager_type, default_manager_type]
|
||||
new_related_manager_info = self.add_new_class_for_current_module(name, bases)
|
||||
|
||||
self.add_new_node_to_model_class(attname, Instance(new_related_manager_info, []))
|
||||
|
||||
|
||||
class AddExtraFieldMethods(ModelClassInitializer):
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import re
|
||||
|
||||
IGNORED_MODULES = {'schema', 'gis_tests', 'admin_widgets', 'admin_filters',
|
||||
'sitemaps_tests', 'staticfiles_tests', 'modeladmin', 'model_forms',
|
||||
'sitemaps_tests', 'staticfiles_tests', 'modeladmin',
|
||||
'generic_views', 'forms_tests', 'flatpages_tests',
|
||||
'admin_ordering', 'admin_changelist', 'admin_views',
|
||||
'invalid_models_tests', 'i18n', 'model_formsets',
|
||||
@@ -178,7 +178,6 @@ IGNORED_ERRORS = {
|
||||
],
|
||||
'files': [
|
||||
'Incompatible types in assignment (expression has type "IOBase", variable has type "File")',
|
||||
'Argument 1 to "write" of "SpooledTemporaryFile"',
|
||||
],
|
||||
'filtered_relation': [
|
||||
'has no attribute "name"',
|
||||
@@ -231,13 +230,8 @@ IGNORED_ERRORS = {
|
||||
],
|
||||
'mail': [
|
||||
'List item 1 has incompatible type "None"; expected "str"',
|
||||
'Argument 1 to "push" of "SMTPChannel" has incompatible type "str"; expected "bytes"',
|
||||
'Value of type "Union[List[Message], str, bytes, None]" is not indexable',
|
||||
'Incompatible types in assignment '
|
||||
+ '(expression has type "bool", variable has type "Union[SMTP_SSL, SMTP, None]")',
|
||||
re.compile(
|
||||
r'Item "(int|str)" of "Union\[Message, str, int, Any\]" has no attribute "(get_content_type|get_filename)"'
|
||||
)
|
||||
],
|
||||
'messages_tests': [
|
||||
'List item 0 has incompatible type "Dict[str, Message]"; expected "Message"',
|
||||
@@ -248,7 +242,7 @@ IGNORED_ERRORS = {
|
||||
re.compile(r'"(HttpRequest|WSGIRequest)" has no attribute'),
|
||||
],
|
||||
'many_to_many': [
|
||||
'(expression has type "List[Article]", variable has type "RelatedManager[Article]"',
|
||||
'(expression has type "List[Article]", variable has type "Article_RelatedManager2',
|
||||
'"add" of "RelatedManager" has incompatible type "Article"; expected "Union[Publication, int]"',
|
||||
],
|
||||
'many_to_one': [
|
||||
@@ -271,7 +265,7 @@ IGNORED_ERRORS = {
|
||||
'"Manager[Any]" has no attribute "args"',
|
||||
'Dict entry 0 has incompatible type "Any"',
|
||||
'Argument 1 to "append" of "list" has incompatible type',
|
||||
'base class "Model" defined the type as "Manager[Any]"',
|
||||
'base class "Model" defined the type as "BaseManager[Any]"',
|
||||
'Argument 1 to "RunPython" has incompatible type "str"',
|
||||
|
||||
],
|
||||
@@ -283,6 +277,14 @@ IGNORED_ERRORS = {
|
||||
'Incompatible type for "size" of "FloatModel" (got "object", expected "Union[float, int, str, Combinable]")',
|
||||
'Incompatible type for "value" of "IntegerModel" (got "object", expected',
|
||||
],
|
||||
'model_forms': [
|
||||
'"render" of "Widget"',
|
||||
"Module 'django.core.validators' has no attribute 'ValidationError'",
|
||||
'Incompatible types in assignment',
|
||||
'NewForm',
|
||||
'"type" has no attribute "base_fields"',
|
||||
'Argument "instance" to "InvalidModelForm" has incompatible type "Type[Category]"',
|
||||
],
|
||||
'model_indexes': [
|
||||
'Argument "condition" to "Index" has incompatible type "str"; expected "Optional[Q]"'
|
||||
],
|
||||
@@ -312,9 +314,6 @@ IGNORED_ERRORS = {
|
||||
'model_enums': [
|
||||
"'bool' is not a valid base class",
|
||||
],
|
||||
'multiple_database': [
|
||||
'Unexpected attribute "extra_arg" for model "Book"'
|
||||
],
|
||||
'null_queries': [
|
||||
"Cannot resolve keyword 'foo' into field"
|
||||
],
|
||||
@@ -458,7 +457,7 @@ IGNORED_ERRORS = {
|
||||
'No overload variant of "join" matches argument types "str", "None"',
|
||||
'Argument 1 to "Archive" has incompatible type "None"; expected "str"',
|
||||
'Argument 1 to "to_path" has incompatible type "int"; expected "Union[Path, str]"',
|
||||
|
||||
'Cannot infer type argument 1 of "cached_property"',
|
||||
],
|
||||
'view_tests': [
|
||||
"Module 'django.views.debug' has no attribute 'Path'",
|
||||
|
||||
@@ -14,8 +14,8 @@ from scripts.enabled_test_modules import (
|
||||
)
|
||||
|
||||
DJANGO_COMMIT_REFS: Dict[str, Tuple[str, str]] = {
|
||||
'2.2': ('stable/2.2.x', 'e8b0903976077b951795938b260211214ed7fe41'),
|
||||
'3.0': ('stable/3.0.x', '7ec5962638144cbf4c2e47ea7d8dc02d1ce44394')
|
||||
'2.2': ('stable/2.2.x', '86befcc172c23170a720b3e0c06db51a99b3da59'),
|
||||
'3.0': ('stable/3.0.x', '6cb30414bc0f83b49afc4cae76d4af5656effe9a')
|
||||
}
|
||||
PROJECT_DIRECTORY = Path(__file__).parent.parent
|
||||
DJANGO_SOURCE_DIRECTORY = PROJECT_DIRECTORY / 'django-sources' # type: Path
|
||||
|
||||
4
setup.py
4
setup.py
@@ -21,14 +21,14 @@ with open('README.md', 'r') as f:
|
||||
readme = f.read()
|
||||
|
||||
dependencies = [
|
||||
'mypy>=0.750,<0.760',
|
||||
'mypy>=0.760,<0.770',
|
||||
'typing-extensions',
|
||||
'django',
|
||||
]
|
||||
|
||||
setup(
|
||||
name="django-stubs",
|
||||
version="1.3.1",
|
||||
version="1.4.0",
|
||||
description='Mypy stubs for Django',
|
||||
long_description=readme,
|
||||
long_description_content_type='text/markdown',
|
||||
|
||||
@@ -648,3 +648,27 @@
|
||||
abstract = True
|
||||
class User(AbstractUser):
|
||||
pass
|
||||
|
||||
|
||||
- case: related_manager_is_a_subclass_of_default_manager
|
||||
main: |
|
||||
from myapp.models import User
|
||||
reveal_type(User().orders) # N: Revealed type is 'myapp.models.Order_RelatedManager'
|
||||
reveal_type(User().orders.get()) # N: Revealed type is 'myapp.models.Order*'
|
||||
reveal_type(User().orders.manager_method()) # N: Revealed type is 'builtins.int'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
class User(models.Model):
|
||||
pass
|
||||
class OrderManager(models.Manager):
|
||||
def manager_method(self) -> int:
|
||||
pass
|
||||
class Order(models.Model):
|
||||
objects = OrderManager()
|
||||
user = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='orders')
|
||||
|
||||
|
||||
@@ -1,4 +1,48 @@
|
||||
- case: test_from_queryset_returns_intersection_of_manager_and_queryset
|
||||
- case: from_queryset_with_base_manager
|
||||
main: |
|
||||
from myapp.models import MyModel
|
||||
reveal_type(MyModel().objects) # N: Revealed type is 'myapp.models.MyModel_NewManager[myapp.models.MyModel]'
|
||||
reveal_type(MyModel().objects.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
||||
reveal_type(MyModel().objects.queryset_method()) # N: Revealed type is 'builtins.str'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from django.db.models.manager import BaseManager
|
||||
|
||||
class ModelQuerySet(models.QuerySet):
|
||||
def queryset_method(self) -> str:
|
||||
return 'hello'
|
||||
NewManager = BaseManager.from_queryset(ModelQuerySet)
|
||||
class MyModel(models.Model):
|
||||
objects = NewManager()
|
||||
|
||||
- case: from_queryset_with_manager
|
||||
main: |
|
||||
from myapp.models import MyModel
|
||||
reveal_type(MyModel().objects) # N: Revealed type is 'myapp.models.MyModel_NewManager[myapp.models.MyModel]'
|
||||
reveal_type(MyModel().objects.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
||||
reveal_type(MyModel().objects.queryset_method()) # N: Revealed type is 'builtins.str'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
|
||||
class ModelQuerySet(models.QuerySet):
|
||||
def queryset_method(self) -> str:
|
||||
return 'hello'
|
||||
|
||||
NewManager = models.Manager.from_queryset(ModelQuerySet)
|
||||
class MyModel(models.Model):
|
||||
objects = NewManager()
|
||||
|
||||
- case: from_queryset_returns_intersection_of_manager_and_queryset
|
||||
main: |
|
||||
from myapp.models import MyModel, NewManager
|
||||
reveal_type(NewManager()) # N: Revealed type is 'myapp.models.NewManager'
|
||||
@@ -24,7 +68,7 @@
|
||||
class MyModel(models.Model):
|
||||
objects = NewManager()
|
||||
|
||||
- case: test_from_queryset_with_class_name_provided
|
||||
- case: from_queryset_with_class_name_provided
|
||||
main: |
|
||||
from myapp.models import MyModel, NewManager
|
||||
reveal_type(NewManager()) # N: Revealed type is 'myapp.models.NewManager'
|
||||
@@ -50,3 +94,88 @@
|
||||
class MyModel(models.Model):
|
||||
objects = NewManager()
|
||||
|
||||
- case: from_queryset_with_class_inheritance
|
||||
main: |
|
||||
from myapp.models import MyModel
|
||||
reveal_type(MyModel().objects) # N: Revealed type is 'myapp.models.MyModel_NewManager[myapp.models.MyModel]'
|
||||
reveal_type(MyModel().objects.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
||||
reveal_type(MyModel().objects.queryset_method()) # N: Revealed type is 'builtins.str'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from django.db.models.manager import BaseManager
|
||||
class BaseQuerySet(models.QuerySet):
|
||||
def queryset_method(self) -> str:
|
||||
return 'hello'
|
||||
class ModelQuerySet(BaseQuerySet):
|
||||
pass
|
||||
|
||||
NewManager = BaseManager.from_queryset(ModelQuerySet)
|
||||
class MyModel(models.Model):
|
||||
objects = NewManager()
|
||||
|
||||
- case: from_queryset_with_manager_in_another_directory_and_imports
|
||||
main: |
|
||||
from myapp.models import MyModel
|
||||
reveal_type(MyModel().objects) # N: Revealed type is 'myapp.models.MyModel_NewManager[myapp.models.MyModel]'
|
||||
reveal_type(MyModel().objects.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
||||
reveal_type(MyModel().objects.queryset_method) # N: Revealed type is 'def (param: Union[builtins.str, None] =) -> Union[builtins.str, None]'
|
||||
reveal_type(MyModel().objects.queryset_method('str')) # N: Revealed type is 'Union[builtins.str, None]'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from myapp.managers import NewManager
|
||||
|
||||
class MyModel(models.Model):
|
||||
objects = NewManager()
|
||||
- path: myapp/managers.py
|
||||
content: |
|
||||
from typing import Optional
|
||||
from django.db import models
|
||||
|
||||
class ModelQuerySet(models.QuerySet):
|
||||
def queryset_method(self, param: Optional[str] = None) -> Optional[str]:
|
||||
return param
|
||||
|
||||
NewManager = models.Manager.from_queryset(ModelQuerySet)
|
||||
|
||||
- case: from_queryset_with_inherited_manager_and_typing_no_return
|
||||
disable_cache: true
|
||||
main: |
|
||||
from myapp.models import MyModel
|
||||
reveal_type(MyModel().objects) # N: Revealed type is 'myapp.models.MyModel_NewManager[myapp.models.MyModel]'
|
||||
reveal_type(MyModel().objects.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
||||
reveal_type(MyModel().objects.base_queryset_method) # N: Revealed type is 'def (param: Union[builtins.int, builtins.str]) -> <nothing>'
|
||||
reveal_type(MyModel().objects.base_queryset_method(2)) # N: Revealed type is '<nothing>'
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from myapp.managers import NewManager
|
||||
class MyModel(models.Model):
|
||||
objects = NewManager()
|
||||
- path: myapp/managers.py
|
||||
content: |
|
||||
from django.db import models
|
||||
from myapp.base_queryset import BaseQuerySet
|
||||
class ModelQuerySet(BaseQuerySet):
|
||||
pass
|
||||
NewManager = models.Manager.from_queryset(ModelQuerySet)
|
||||
- path: myapp/base_queryset.py
|
||||
content: |
|
||||
from typing import NoReturn, Union
|
||||
from django.db import models
|
||||
class BaseQuerySet(models.QuerySet):
|
||||
def base_queryset_method(self, param: Union[int, str]) -> NoReturn:
|
||||
raise ValueError
|
||||
@@ -48,7 +48,7 @@
|
||||
class Base(Generic[_T]):
|
||||
def __init__(self, model_cls: Type[_T]):
|
||||
self.model_cls = model_cls
|
||||
reveal_type(self.model_cls._default_manager) # N: Revealed type is 'django.db.models.manager.Manager[django.db.models.base.Model]'
|
||||
reveal_type(self.model_cls._default_manager) # N: Revealed type is 'django.db.models.manager.BaseManager[django.db.models.base.Model]'
|
||||
class MyModel(models.Model):
|
||||
pass
|
||||
class Child(Base[MyModel]):
|
||||
@@ -307,15 +307,15 @@
|
||||
- case: custom_manager_returns_proper_model_types
|
||||
main: |
|
||||
from myapp.models import User
|
||||
reveal_type(User.objects) # N: Revealed type is 'myapp.models.User_MyManager[myapp.models.User]'
|
||||
reveal_type(User.objects.select_related()) # N: Revealed type is 'myapp.models.User_MyManager[myapp.models.User]'
|
||||
reveal_type(User.objects) # N: Revealed type is 'myapp.models.User_MyManager2[myapp.models.User]'
|
||||
reveal_type(User.objects.select_related()) # N: Revealed type is 'myapp.models.User_MyManager2[myapp.models.User]'
|
||||
reveal_type(User.objects.get()) # N: Revealed type is 'myapp.models.User*'
|
||||
reveal_type(User.objects.get_instance()) # N: Revealed type is 'builtins.int'
|
||||
reveal_type(User.objects.get_instance_untyped('hello')) # N: Revealed type is 'Any'
|
||||
|
||||
from myapp.models import ChildUser
|
||||
reveal_type(ChildUser.objects) # N: Revealed type is 'myapp.models.ChildUser_MyManager[myapp.models.ChildUser]'
|
||||
reveal_type(ChildUser.objects.select_related()) # N: Revealed type is 'myapp.models.ChildUser_MyManager[myapp.models.ChildUser]'
|
||||
reveal_type(ChildUser.objects) # N: Revealed type is 'myapp.models.ChildUser_MyManager2[myapp.models.ChildUser]'
|
||||
reveal_type(ChildUser.objects.select_related()) # N: Revealed type is 'myapp.models.ChildUser_MyManager2[myapp.models.ChildUser]'
|
||||
reveal_type(ChildUser.objects.get()) # N: Revealed type is 'myapp.models.ChildUser*'
|
||||
reveal_type(ChildUser.objects.get_instance()) # N: Revealed type is 'builtins.int'
|
||||
reveal_type(ChildUser.objects.get_instance_untyped('hello')) # N: Revealed type is 'Any'
|
||||
|
||||
18
test-data/typecheck/utils/test_functional.yml
Normal file
18
test-data/typecheck/utils/test_functional.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
- case: cached_property_class_vs_instance_attributes
|
||||
main: |
|
||||
from django.utils.functional import cached_property
|
||||
from typing import List
|
||||
|
||||
class Foo:
|
||||
@cached_property
|
||||
def attr(self) -> List[str]: ...
|
||||
|
||||
reveal_type(attr) # N: Revealed type is 'django.utils.functional.cached_property[builtins.list*[builtins.str]]'
|
||||
reveal_type(attr.name) # N: Revealed type is 'builtins.str'
|
||||
|
||||
reveal_type(Foo.attr) # N: Revealed type is 'django.utils.functional.cached_property[builtins.list*[builtins.str]]'
|
||||
reveal_type(Foo.attr.func) # N: Revealed type is 'def (*Any, **Any) -> builtins.list*[builtins.str]'
|
||||
|
||||
f = Foo()
|
||||
reveal_type(f.attr) # N: Revealed type is 'builtins.list*[builtins.str]'
|
||||
f.attr.name # E: "List[str]" has no attribute "name"
|
||||
Reference in New Issue
Block a user