mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-11 06:21:58 +08:00
Compare commits
47 Commits
newsemanal
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d2534ea8d | ||
|
|
54f5f63e71 | ||
|
|
4c5723d368 | ||
|
|
7e0e43135d | ||
|
|
e05b84e32d | ||
|
|
71751d3795 | ||
|
|
25f92e8e56 | ||
|
|
28d47c7e93 | ||
|
|
197cb4058e | ||
|
|
dac2b31fb2 | ||
|
|
8d2600136a | ||
|
|
570772f973 | ||
|
|
d5c1bfb12a | ||
|
|
64cbb0f70e | ||
|
|
6f5a39625e | ||
|
|
bf604a0398 | ||
|
|
92c8dfc93f | ||
|
|
c10c55052c | ||
|
|
96914e466b | ||
|
|
90ed7f332d | ||
|
|
a801501151 | ||
|
|
8ea59985df | ||
|
|
2964ed53d7 | ||
|
|
1b9176f994 | ||
|
|
54d0d018c6 | ||
|
|
1af3a12f2c | ||
|
|
7af89ee6a6 | ||
|
|
afa16bfb74 | ||
|
|
f77073157b | ||
|
|
fe3b95c611 | ||
|
|
d0f9730c53 | ||
|
|
0fdd678d65 | ||
|
|
2397065fa6 | ||
|
|
04023a9f31 | ||
|
|
95e6c94319 | ||
|
|
d96aee7a8b | ||
|
|
2489bb9b04 | ||
|
|
3a8f278c88 | ||
|
|
85b65b4578 | ||
|
|
150e8e862a | ||
|
|
5b3088a17a | ||
|
|
f89a0fbbaa | ||
|
|
438f8b5829 | ||
|
|
836d5acd8f | ||
|
|
bfae51e64c | ||
|
|
e3801918e3 | ||
|
|
6f296b0a91 |
@@ -4,10 +4,19 @@ dist: xenial
|
|||||||
sudo: required
|
sudo: required
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
|
- name: Run plugin test suite with python 3.8
|
||||||
|
python: 3.8
|
||||||
|
script: 'pytest'
|
||||||
|
|
||||||
- name: Run plugin test suite with python 3.7
|
- name: Run plugin test suite with python 3.7
|
||||||
python: 3.7
|
python: 3.7
|
||||||
script: 'pytest'
|
script: 'pytest'
|
||||||
|
|
||||||
|
- name: Typecheck Django 3.0 test suite with python 3.8
|
||||||
|
python: 3.8
|
||||||
|
script: |
|
||||||
|
python ./scripts/typecheck_tests.py --django_version=3.0
|
||||||
|
|
||||||
- name: Typecheck Django 3.0 test suite with python 3.7
|
- name: Typecheck Django 3.0 test suite with python 3.7
|
||||||
python: 3.7
|
python: 3.7
|
||||||
script: |
|
script: |
|
||||||
|
|||||||
111
CONTRIBUTING.md
Normal file
111
CONTRIBUTING.md
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# How to contribute
|
||||||
|
|
||||||
|
## Tutorials
|
||||||
|
|
||||||
|
If you want to start working on this project,
|
||||||
|
you will need to get familiar with these projects:
|
||||||
|
|
||||||
|
- [Django docs](https://docs.djangoproject.com/en/dev/)
|
||||||
|
- [Typing in Python](https://inventwithpython.com/blog/2019/11/24/type-hints-for-busy-python-programmers/)
|
||||||
|
- [How to write custom mypy plugins](https://mypy.readthedocs.io/en/stable/extending_mypy.html)
|
||||||
|
- [Typechecking Django and DRF](https://sobolevn.me/2019/08/typechecking-django-and-drf) guide
|
||||||
|
- [Testing mypy stubs, plugins, and types](https://sobolevn.me/2019/08/testing-mypy-types) guide
|
||||||
|
|
||||||
|
It is also recommended to take a look at these resources:
|
||||||
|
|
||||||
|
- [Awesome Python Typing](https://github.com/typeddjango/awesome-python-typing)
|
||||||
|
|
||||||
|
|
||||||
|
## Dev documentation
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
We use `pip` to manage the dependencies.
|
||||||
|
|
||||||
|
To install them you would need to activate your `virtualenv` and run `install` command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r ./dev-requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Tests and linters
|
||||||
|
|
||||||
|
We use `mypy`, `pytest`, `flake8`, and `black` for quality control.
|
||||||
|
Here's [how we run our CI](https://github.com/typeddjango/django-stubs/blob/master/.travis.yml).
|
||||||
|
|
||||||
|
### Typechecking
|
||||||
|
|
||||||
|
To run typechecking use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mypy ./mypy_django_plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
There are unit tests and type-related tests.
|
||||||
|
|
||||||
|
To run unit tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
Type-related tests ensure that different Django versions do work correctly.
|
||||||
|
To run type-related tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python ./scripts/typecheck_tests.py --django_version=2.2
|
||||||
|
python ./scripts/typecheck_tests.py --django_version=3.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Currently we only support two Django versions.
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
|
||||||
|
To run auto-formatting:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
isort -rc .
|
||||||
|
black django-stubs/
|
||||||
|
```
|
||||||
|
|
||||||
|
To run linting:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
flake8
|
||||||
|
flake8 --config flake8-pyi.ini
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Submitting your code
|
||||||
|
|
||||||
|
We use [trunk based](https://trunkbaseddevelopment.com/)
|
||||||
|
development (we also sometimes call it `wemake-git-flow`).
|
||||||
|
|
||||||
|
What the point of this method?
|
||||||
|
|
||||||
|
1. We use protected `master` branch,
|
||||||
|
so the only way to push your code is via pull request
|
||||||
|
2. We use issue branches: to implement a new feature or to fix a bug
|
||||||
|
create a new branch named `issue-$TASKNUMBER`
|
||||||
|
3. Then create a pull request to `master` branch
|
||||||
|
4. We use `git tag`s to make releases, so we can track what has changed
|
||||||
|
since the latest release
|
||||||
|
|
||||||
|
So, this way we achieve an easy and scalable development process
|
||||||
|
which frees us from merging hell and long-living branches.
|
||||||
|
|
||||||
|
In this method, the latest version of the app is always in the `master` branch.
|
||||||
|
|
||||||
|
|
||||||
|
## Other help
|
||||||
|
|
||||||
|
You can contribute by spreading a word about this library.
|
||||||
|
It would also be a huge contribution to write
|
||||||
|
a short article on how you are using this project.
|
||||||
|
You can also share your best practices with us.
|
||||||
113
README.md
113
README.md
@@ -1,14 +1,12 @@
|
|||||||
<img src="http://mypy-lang.org/static/mypy_light.svg" alt="mypy logo" width="300px"/>
|
<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)
|
[](https://travis-ci.com/typeddjango/django-stubs)
|
||||||
[](http://mypy-lang.org/)
|
[](http://mypy-lang.org/)
|
||||||
[](https://gitter.im/mypy-django/Lobby)
|
[](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.
|
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.
|
||||||
|
|
||||||
Could be run on earlier versions of Django, but expect some missing imports warnings.
|
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@@ -17,69 +15,106 @@ Could be run on earlier versions of Django, but expect some missing imports warn
|
|||||||
pip install django-stubs
|
pip install django-stubs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See [Configuration](#configuration) section to get started.
|
||||||
## Mypy compatibility
|
|
||||||
|
|
||||||
| django-stubs | mypy version | django version | python version
|
|
||||||
| ------------ | ---- | ---- | ---- |
|
|
||||||
| 1.3.0 | 0.750 | 2.2.x | ^3.6
|
|
||||||
| 1.2.0 | 0.730 | 2.2.x | ^3.6
|
|
||||||
| 1.1.0 | 0.720 | 2.2.x | ^3.6
|
|
||||||
| 0.12.x | old semantic analyzer (<0.711), dmypy support | 2.1.x | ^3.6
|
|
||||||
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
To make mypy aware of the plugin, you need to add
|
To make `mypy` happy, you will need to add:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[mypy]
|
[mypy]
|
||||||
plugins =
|
plugins =
|
||||||
mypy_django_plugin.main
|
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).
|
in your `mypy.ini` or `setup.cfg` [file](https://mypy.readthedocs.io/en/latest/config_file.html).
|
||||||
|
|
||||||
Plugin also requires Django settings module (what you put into `DJANGO_SETTINGS_MODULE` variable) to be specified.
|
Two things happeining here:
|
||||||
|
|
||||||
```ini
|
1. We need to explicitly list our plugin to be loaded by `mypy`
|
||||||
[mypy]
|
2. Our plugin also requires `django` settings module (what you put into `DJANGO_SETTINGS_MODULE` variable) to be specified
|
||||||
strict_optional = True
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
This fully working [typed boilerplate](https://github.com/wemake-services/wemake-django-template) can serve you as an example.
|
This fully working [typed boilerplate](https://github.com/wemake-services/wemake-django-template) can serve you as an example.
|
||||||
|
|
||||||
|
|
||||||
## Notes
|
## Version compatibility
|
||||||
|
|
||||||
Type implementation monkey-patches Django to add `__class_getitem__` to the `Manager` class.
|
We rely on different `django` and `mypy` versions:
|
||||||
If you would use Python3.7 and do that too in your code, you can make things like
|
|
||||||
|
| django-stubs | mypy version | django version | python version
|
||||||
|
| ------------ | ---- | ---- | ---- |
|
||||||
|
| 1.5.0 | 0.780 | 2.2.x \|\| 3.x | ^3.6
|
||||||
|
| 1.4.0 | 0.770 | 2.2.x \|\| 3.x | ^3.6
|
||||||
|
| 1.3.0 | 0.750 | 2.2.x \|\| 3.x | ^3.6
|
||||||
|
| 1.2.0 | 0.730 | 2.2.x | ^3.6
|
||||||
|
| 1.1.0 | 0.720 | 2.2.x | ^3.6
|
||||||
|
| 0.12.x | old semantic analyzer (<0.711), dmypy support | 2.1.x | ^3.6
|
||||||
|
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### Is this an official Django project?
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Is it safe to use this in production?
|
||||||
|
|
||||||
|
Yes, it is! This project does not affect your runtime at all.
|
||||||
|
It only affects `mypy` type checking process.
|
||||||
|
|
||||||
|
But, it does not make any sense to use this project without `mypy`.
|
||||||
|
|
||||||
|
### 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 or `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.
|
||||||
|
|
||||||
|
### 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]`.
|
||||||
|
|
||||||
|
This happens because Django classes do not support [`__class_getitem__`](https://www.python.org/dev/peps/pep-0560/#class-getitem) magic method.
|
||||||
|
|
||||||
|
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
|
```python
|
||||||
class MyUserManager(models.Manager['MyUser']):
|
from django.http import HttpRequest
|
||||||
pass
|
from my_user_app.models import MyUser
|
||||||
|
|
||||||
class MyUser(models.Model):
|
class MyRequest(HttpRequest):
|
||||||
objects = MyUserManager()
|
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
|
## To get help
|
||||||
|
|
||||||
We have Gitter here: <https://gitter.im/mypy-django/Lobby>
|
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,8 +1,8 @@
|
|||||||
black
|
black
|
||||||
pytest-mypy-plugins==1.2.0
|
pytest-mypy-plugins==1.3.0
|
||||||
psycopg2
|
psycopg2
|
||||||
flake8==3.7.9
|
flake8==3.7.9
|
||||||
flake8-pyi==19.3.0
|
flake8-pyi==19.3.0
|
||||||
isort==4.3.21
|
isort==4.3.21
|
||||||
gitpython==3.0.5
|
gitpython==3.1.0
|
||||||
-e .
|
-e .
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ from django.http.response import HttpResponse, HttpResponseBase
|
|||||||
|
|
||||||
from django.urls import URLResolver, URLPattern
|
from django.urls import URLResolver, URLPattern
|
||||||
|
|
||||||
handler400: Callable[..., HttpResponse] = ...
|
handler400: Union[str, Callable[..., HttpResponse]] = ...
|
||||||
handler403: Callable[..., HttpResponse] = ...
|
handler403: Union[str, Callable[..., HttpResponse]] = ...
|
||||||
handler404: Callable[..., HttpResponse] = ...
|
handler404: Union[str, Callable[..., HttpResponse]] = ...
|
||||||
handler500: Callable[..., HttpResponse] = ...
|
handler500: Union[str, Callable[..., HttpResponse]] = ...
|
||||||
|
|
||||||
IncludedURLConf = Tuple[List[URLResolver], Optional[str], Optional[str]]
|
IncludedURLConf = Tuple[List[URLResolver], Optional[str], Optional[str]]
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union, Iterable
|
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union, Iterable
|
||||||
|
|
||||||
from django.contrib.auth.forms import AdminPasswordChangeForm
|
|
||||||
from django.forms.boundfield import BoundField
|
from django.forms.boundfield import BoundField
|
||||||
|
from django.forms.forms import BaseForm
|
||||||
from django.forms.utils import ErrorDict
|
from django.forms.utils import ErrorDict
|
||||||
from django.forms.widgets import Media, Widget
|
from django.forms.widgets import Media, Widget
|
||||||
from django.utils.safestring import SafeText
|
from django.utils.safestring import SafeText
|
||||||
@@ -23,7 +23,7 @@ class AdminForm:
|
|||||||
readonly_fields: Any = ...
|
readonly_fields: Any = ...
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
form: AdminPasswordChangeForm,
|
form: BaseForm,
|
||||||
fieldsets: List[Tuple[None, Dict[str, List[str]]]],
|
fieldsets: List[Tuple[None, Dict[str, List[str]]]],
|
||||||
prepopulated_fields: Dict[Any, Any],
|
prepopulated_fields: Dict[Any, Any],
|
||||||
readonly_fields: Optional[Iterable[Any]] = ...,
|
readonly_fields: Optional[Iterable[Any]] = ...,
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union
|
from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union, Mapping, TypeVar
|
||||||
|
|
||||||
|
from django.forms.forms import BaseForm
|
||||||
|
from django.forms.formsets import BaseFormSet
|
||||||
|
from typing_extensions import Literal, TypedDict
|
||||||
|
|
||||||
from django.contrib.admin.filters import ListFilter
|
from django.contrib.admin.filters import ListFilter
|
||||||
from django.contrib.admin.models import LogEntry
|
from django.contrib.admin.models import LogEntry
|
||||||
@@ -26,8 +30,10 @@ from django.db.models.fields import Field
|
|||||||
|
|
||||||
IS_POPUP_VAR: str
|
IS_POPUP_VAR: str
|
||||||
TO_FIELD_VAR: str
|
TO_FIELD_VAR: str
|
||||||
HORIZONTAL: Any
|
HORIZONTAL: Literal[1] = ...
|
||||||
VERTICAL: Any
|
VERTICAL: Literal[2] = ...
|
||||||
|
|
||||||
|
_Direction = Union[Literal[1], Literal[2]]
|
||||||
|
|
||||||
def get_content_type_for_model(obj: Union[Type[Model], Model]) -> ContentType: ...
|
def get_content_type_for_model(obj: Union[Type[Model], Model]) -> ContentType: ...
|
||||||
def get_ul_class(radio_style: int) -> str: ...
|
def get_ul_class(radio_style: int) -> str: ...
|
||||||
@@ -37,21 +43,35 @@ class IncorrectLookupParameters(Exception): ...
|
|||||||
FORMFIELD_FOR_DBFIELD_DEFAULTS: Any
|
FORMFIELD_FOR_DBFIELD_DEFAULTS: Any
|
||||||
csrf_protect_m: Any
|
csrf_protect_m: Any
|
||||||
|
|
||||||
|
class _OptionalFieldOpts(TypedDict, total=False):
|
||||||
|
classes: Sequence[str]
|
||||||
|
description: str
|
||||||
|
|
||||||
|
class _FieldOpts(_OptionalFieldOpts, total=True):
|
||||||
|
fields: Sequence[Union[str, Sequence[str]]]
|
||||||
|
|
||||||
|
# Workaround for mypy issue, a Sequence type should be preferred here.
|
||||||
|
# https://github.com/python/mypy/issues/8921
|
||||||
|
# _FieldsetSpec = Sequence[Tuple[Optional[str], _FieldOpts]]
|
||||||
|
_T = TypeVar("_T")
|
||||||
|
_ListOrTuple = Union[Tuple[_T, ...], List[_T]]
|
||||||
|
_FieldsetSpec = _ListOrTuple[Tuple[Optional[str], _FieldOpts]]
|
||||||
|
|
||||||
class BaseModelAdmin:
|
class BaseModelAdmin:
|
||||||
autocomplete_fields: Any = ...
|
autocomplete_fields: Sequence[str] = ...
|
||||||
raw_id_fields: Any = ...
|
raw_id_fields: Sequence[str] = ...
|
||||||
fields: Any = ...
|
fields: Sequence[Union[str, Sequence[str]]] = ...
|
||||||
exclude: Any = ...
|
exclude: Sequence[str] = ...
|
||||||
fieldsets: Any = ...
|
fieldsets: _FieldsetSpec = ...
|
||||||
form: Any = ...
|
form: Type[BaseForm] = ...
|
||||||
filter_vertical: Any = ...
|
filter_vertical: Sequence[str] = ...
|
||||||
filter_horizontal: Any = ...
|
filter_horizontal: Sequence[str] = ...
|
||||||
radio_fields: Any = ...
|
radio_fields: Mapping[str, _Direction] = ...
|
||||||
prepopulated_fields: Any = ...
|
prepopulated_fields: Mapping[str, Sequence[str]] = ...
|
||||||
formfield_overrides: Any = ...
|
formfield_overrides: Mapping[Type[Field], Mapping[str, Any]] = ...
|
||||||
readonly_fields: Any = ...
|
readonly_fields: Sequence[Union[str, Callable[[Model], Any]]] = ...
|
||||||
ordering: Any = ...
|
ordering: Sequence[str] = ...
|
||||||
sortable_by: Any = ...
|
sortable_by: Sequence[str] = ...
|
||||||
view_on_site: bool = ...
|
view_on_site: bool = ...
|
||||||
show_full_result_count: bool = ...
|
show_full_result_count: bool = ...
|
||||||
checks_class: Any = ...
|
checks_class: Any = ...
|
||||||
@@ -93,7 +113,7 @@ class BaseModelAdmin:
|
|||||||
def has_module_permission(self, request: HttpRequest) -> bool: ...
|
def has_module_permission(self, request: HttpRequest) -> bool: ...
|
||||||
|
|
||||||
class ModelAdmin(BaseModelAdmin):
|
class ModelAdmin(BaseModelAdmin):
|
||||||
list_display: Sequence[Union[str, Callable]] = ...
|
list_display: Sequence[Union[str, Callable[[Model], Any]]] = ...
|
||||||
list_display_links: Optional[Sequence[Union[str, Callable]]] = ...
|
list_display_links: Optional[Sequence[Union[str, Callable]]] = ...
|
||||||
list_filter: Sequence[Union[str, Type[ListFilter], Tuple[str, Type[ListFilter]]]] = ...
|
list_filter: Sequence[Union[str, Type[ListFilter], Tuple[str, Type[ListFilter]]]] = ...
|
||||||
list_select_related: Union[bool, Sequence[str]] = ...
|
list_select_related: Union[bool, Sequence[str]] = ...
|
||||||
@@ -101,21 +121,21 @@ class ModelAdmin(BaseModelAdmin):
|
|||||||
list_max_show_all: int = ...
|
list_max_show_all: int = ...
|
||||||
list_editable: Sequence[str] = ...
|
list_editable: Sequence[str] = ...
|
||||||
search_fields: Sequence[str] = ...
|
search_fields: Sequence[str] = ...
|
||||||
date_hierarchy: Optional[Any] = ...
|
date_hierarchy: Optional[str] = ...
|
||||||
save_as: bool = ...
|
save_as: bool = ...
|
||||||
save_as_continue: bool = ...
|
save_as_continue: bool = ...
|
||||||
save_on_top: bool = ...
|
save_on_top: bool = ...
|
||||||
paginator: Any = ...
|
paginator: Type = ...
|
||||||
preserve_filters: bool = ...
|
preserve_filters: bool = ...
|
||||||
inlines: Sequence[Type[InlineModelAdmin]] = ...
|
inlines: Sequence[Type[InlineModelAdmin]] = ...
|
||||||
add_form_template: Any = ...
|
add_form_template: str = ...
|
||||||
change_form_template: Any = ...
|
change_form_template: str = ...
|
||||||
change_list_template: Any = ...
|
change_list_template: str = ...
|
||||||
delete_confirmation_template: Any = ...
|
delete_confirmation_template: str = ...
|
||||||
delete_selected_confirmation_template: Any = ...
|
delete_selected_confirmation_template: str = ...
|
||||||
object_history_template: Any = ...
|
object_history_template: str = ...
|
||||||
popup_response_template: Any = ...
|
popup_response_template: str = ...
|
||||||
actions: Any = ...
|
actions: Sequence[Callable[[ModelAdmin, HttpRequest, QuerySet], None]] = ...
|
||||||
action_form: Any = ...
|
action_form: Any = ...
|
||||||
actions_on_top: bool = ...
|
actions_on_top: bool = ...
|
||||||
actions_on_bottom: bool = ...
|
actions_on_bottom: bool = ...
|
||||||
@@ -227,9 +247,9 @@ class ModelAdmin(BaseModelAdmin):
|
|||||||
def history_view(self, request: HttpRequest, object_id: str, extra_context: None = ...) -> HttpResponse: ...
|
def history_view(self, request: HttpRequest, object_id: str, extra_context: None = ...) -> HttpResponse: ...
|
||||||
|
|
||||||
class InlineModelAdmin(BaseModelAdmin):
|
class InlineModelAdmin(BaseModelAdmin):
|
||||||
model: Any = ...
|
model: Type[Model] = ...
|
||||||
fk_name: Any = ...
|
fk_name: str = ...
|
||||||
formset: Any = ...
|
formset: BaseFormSet = ...
|
||||||
extra: int = ...
|
extra: int = ...
|
||||||
min_num: Optional[int] = ...
|
min_num: Optional[int] = ...
|
||||||
max_num: Optional[int] = ...
|
max_num: Optional[int] = ...
|
||||||
@@ -238,8 +258,8 @@ class InlineModelAdmin(BaseModelAdmin):
|
|||||||
verbose_name_plural: Optional[str] = ...
|
verbose_name_plural: Optional[str] = ...
|
||||||
can_delete: bool = ...
|
can_delete: bool = ...
|
||||||
show_change_link: bool = ...
|
show_change_link: bool = ...
|
||||||
classes: Any = ...
|
classes: Optional[Sequence[str]] = ...
|
||||||
admin_site: Any = ...
|
admin_site: AdminSite = ...
|
||||||
parent_model: Any = ...
|
parent_model: Any = ...
|
||||||
opts: Any = ...
|
opts: Any = ...
|
||||||
has_registered_model: Any = ...
|
has_registered_model: Any = ...
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class AdminSite:
|
|||||||
def i18n_javascript(self, request: WSGIRequest, extra_context: Optional[Dict[Any, Any]] = ...) -> HttpResponse: ...
|
def i18n_javascript(self, request: WSGIRequest, extra_context: Optional[Dict[Any, Any]] = ...) -> HttpResponse: ...
|
||||||
def logout(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> TemplateResponse: ...
|
def logout(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> TemplateResponse: ...
|
||||||
def login(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> HttpResponse: ...
|
def login(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> HttpResponse: ...
|
||||||
|
def _build_app_dict(self, request: WSGIRequest, label: Optional[str] = ...) -> Dict[str, Any]: ...
|
||||||
def get_app_list(self, request: WSGIRequest) -> List[Any]: ...
|
def get_app_list(self, request: WSGIRequest) -> List[Any]: ...
|
||||||
def index(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> TemplateResponse: ...
|
def index(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> TemplateResponse: ...
|
||||||
def app_index(
|
def app_index(
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
|
import sys
|
||||||
from typing import Any, Optional, Tuple, List, overload, TypeVar
|
from typing import Any, Optional, Tuple, List, overload, TypeVar
|
||||||
|
|
||||||
from django.db.models.base import Model
|
from django.db.models.base import Model
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
if sys.version_info < (3, 8):
|
||||||
|
from typing_extensions import Literal
|
||||||
|
else:
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
_T = TypeVar("_T", bound=Model)
|
_T = TypeVar("_T", bound=Model)
|
||||||
|
|
||||||
class BaseUserManager(models.Manager[_T]):
|
class BaseUserManager(models.Manager[_T]):
|
||||||
@@ -20,9 +26,9 @@ class AbstractBaseUser(models.Model):
|
|||||||
def get_username(self) -> str: ...
|
def get_username(self) -> str: ...
|
||||||
def natural_key(self) -> Tuple[str]: ...
|
def natural_key(self) -> Tuple[str]: ...
|
||||||
@property
|
@property
|
||||||
def is_anonymous(self) -> bool: ...
|
def is_anonymous(self) -> Literal[False]: ...
|
||||||
@property
|
@property
|
||||||
def is_authenticated(self) -> bool: ...
|
def is_authenticated(self) -> Literal[True]: ...
|
||||||
def set_password(self, raw_password: Optional[str]) -> None: ...
|
def set_password(self, raw_password: Optional[str]) -> None: ...
|
||||||
def check_password(self, raw_password: str) -> bool: ...
|
def check_password(self, raw_password: str) -> bool: ...
|
||||||
def set_unusable_password(self) -> None: ...
|
def set_unusable_password(self) -> None: ...
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
from typing import Callable, List, Optional, Set, Union
|
from typing import Callable, List, Optional, Set, Union, TypeVar, overload
|
||||||
|
|
||||||
from django.contrib.auth import REDIRECT_FIELD_NAME as REDIRECT_FIELD_NAME # noqa: F401
|
from django.contrib.auth import REDIRECT_FIELD_NAME as REDIRECT_FIELD_NAME # noqa: F401
|
||||||
|
from django.http.response import HttpResponseBase
|
||||||
|
|
||||||
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
|
||||||
|
_VIEW = TypeVar("_VIEW", bound=Callable[..., HttpResponseBase])
|
||||||
|
|
||||||
def user_passes_test(
|
def user_passes_test(
|
||||||
test_func: Callable, login_url: Optional[str] = ..., redirect_field_name: str = ...
|
test_func: Callable[[AbstractUser], bool], login_url: Optional[str] = ..., redirect_field_name: str = ...
|
||||||
) -> Callable: ...
|
) -> Callable[[_VIEW], _VIEW]: ...
|
||||||
def login_required(
|
|
||||||
function: Optional[Callable] = ..., redirect_field_name: str = ..., login_url: Optional[str] = ...
|
# There are two ways of calling @login_required: @with(arguments) and @bare
|
||||||
) -> Callable: ...
|
@overload
|
||||||
|
def login_required(redirect_field_name: str = ..., login_url: Optional[str] = ...) -> Callable[[_VIEW], _VIEW]: ...
|
||||||
|
@overload
|
||||||
|
def login_required(function: _VIEW, redirect_field_name: str = ..., login_url: Optional[str] = ...) -> _VIEW: ...
|
||||||
def permission_required(
|
def permission_required(
|
||||||
perm: Union[List[str], Set[str], str], login_url: None = ..., raise_exception: bool = ...
|
perm: Union[List[str], Set[str], str], login_url: None = ..., raise_exception: bool = ...
|
||||||
) -> Callable: ...
|
) -> Callable[[_VIEW], _VIEW]: ...
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import sys
|
||||||
from typing import Any, Collection, Optional, Set, Tuple, Type, TypeVar, Union
|
from typing import Any, Collection, Optional, Set, Tuple, Type, TypeVar, Union
|
||||||
|
|
||||||
from django.contrib.auth.backends import ModelBackend
|
from django.contrib.auth.backends import ModelBackend
|
||||||
@@ -9,6 +10,11 @@ from django.db.models.manager import EmptyManager
|
|||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
if sys.version_info < (3, 8):
|
||||||
|
from typing_extensions import Literal
|
||||||
|
else:
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
_AnyUser = Union[Model, "AnonymousUser"]
|
_AnyUser = Union[Model, "AnonymousUser"]
|
||||||
|
|
||||||
def update_last_login(sender: Type[AbstractBaseUser], user: AbstractBaseUser, **kwargs: Any) -> None: ...
|
def update_last_login(sender: Type[AbstractBaseUser], user: AbstractBaseUser, **kwargs: Any) -> None: ...
|
||||||
@@ -105,7 +111,7 @@ class AnonymousUser:
|
|||||||
def has_perms(self, perm_list: Collection[str], obj: Optional[_AnyUser] = ...) -> bool: ...
|
def has_perms(self, perm_list: Collection[str], obj: Optional[_AnyUser] = ...) -> bool: ...
|
||||||
def has_module_perms(self, module: str) -> bool: ...
|
def has_module_perms(self, module: str) -> bool: ...
|
||||||
@property
|
@property
|
||||||
def is_anonymous(self) -> bool: ...
|
def is_anonymous(self) -> Literal[True]: ...
|
||||||
@property
|
@property
|
||||||
def is_authenticated(self) -> bool: ...
|
def is_authenticated(self) -> Literal[False]: ...
|
||||||
def get_username(self) -> str: ...
|
def get_username(self) -> str: ...
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
from datetime import date, datetime as datetime
|
from datetime import date, datetime as datetime
|
||||||
from decimal import Decimal
|
from typing import Any, Optional, SupportsInt, Union
|
||||||
from typing import Any, Optional, Union
|
|
||||||
|
|
||||||
register: Any
|
register: Any
|
||||||
|
|
||||||
def ordinal(value: Optional[str]) -> Optional[str]: ...
|
def ordinal(value: Optional[Union[str, SupportsInt]]) -> Optional[str]: ...
|
||||||
def intcomma(value: Optional[Union[Decimal, float, str]], use_l10n: bool = ...) -> str: ...
|
def intcomma(value: Optional[Union[str, SupportsInt]], use_l10n: bool = ...) -> str: ...
|
||||||
|
|
||||||
intword_converters: Any
|
intword_converters: Any
|
||||||
|
|
||||||
def intword(value: Optional[str]) -> Optional[Union[int, str]]: ...
|
def intword(value: Optional[Union[str, SupportsInt]]) -> Optional[Union[int, str]]: ...
|
||||||
def apnumber(value: Optional[str]) -> Optional[Union[int, str]]: ...
|
def apnumber(value: Optional[Union[str, SupportsInt]]) -> Optional[Union[int, str]]: ...
|
||||||
def naturalday(value: Optional[Union[date, str]], arg: None = ...) -> Optional[str]: ...
|
def naturalday(value: Optional[Union[date, str]], arg: None = ...) -> Optional[str]: ...
|
||||||
def naturaltime(value: datetime) -> str: ...
|
def naturaltime(value: datetime) -> str: ...
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Dict, List, Optional, Union
|
from typing import Any, Dict, List, Optional, Union, Protocol
|
||||||
|
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.contrib.sites.requests import RequestSite
|
from django.contrib.sites.requests import RequestSite
|
||||||
@@ -13,10 +13,19 @@ class SitemapNotFound(Exception): ...
|
|||||||
|
|
||||||
def ping_google(sitemap_url: Optional[str] = ..., ping_url: str = ...) -> None: ...
|
def ping_google(sitemap_url: Optional[str] = ..., ping_url: str = ...) -> None: ...
|
||||||
|
|
||||||
|
class _SupportsLen(Protocol):
|
||||||
|
def __len__(self) -> int: ...
|
||||||
|
|
||||||
|
class _SupportsCount(Protocol):
|
||||||
|
def count(self) -> int: ...
|
||||||
|
|
||||||
|
class _SupportsOrdered(Protocol):
|
||||||
|
ordered: bool = ...
|
||||||
|
|
||||||
class Sitemap:
|
class Sitemap:
|
||||||
limit: int = ...
|
limit: int = ...
|
||||||
protocol: Optional[str] = ...
|
protocol: Optional[str] = ...
|
||||||
def items(self) -> List[Any]: ...
|
def items(self) -> Union[_SupportsLen, _SupportsCount, _SupportsOrdered]: ...
|
||||||
def location(self, obj: Model) -> str: ...
|
def location(self, obj: Model) -> str: ...
|
||||||
@property
|
@property
|
||||||
def paginator(self) -> Paginator: ...
|
def paginator(self) -> Paginator: ...
|
||||||
|
|||||||
6
django-stubs/core/cache/backends/base.pyi
vendored
6
django-stubs/core/cache/backends/base.pyi
vendored
@@ -19,16 +19,16 @@ class BaseCache:
|
|||||||
def __init__(self, params: Dict[str, Any]) -> None: ...
|
def __init__(self, params: Dict[str, Any]) -> None: ...
|
||||||
def get_backend_timeout(self, timeout: Any = ...) -> Optional[float]: ...
|
def get_backend_timeout(self, timeout: Any = ...) -> Optional[float]: ...
|
||||||
def make_key(self, key: Any, version: Optional[Any] = ...) -> str: ...
|
def make_key(self, key: Any, version: Optional[Any] = ...) -> str: ...
|
||||||
def add(self, key: Any, value: Any, timeout: Any = ..., version: Optional[Any] = ...) -> None: ...
|
def add(self, key: Any, value: Any, timeout: Any = ..., version: Optional[Any] = ...) -> bool: ...
|
||||||
def get(self, key: Any, default: Optional[Any] = ..., version: Optional[Any] = ...) -> Any: ...
|
def get(self, key: Any, default: Optional[Any] = ..., version: Optional[Any] = ...) -> Any: ...
|
||||||
def set(self, key: Any, value: Any, timeout: Any = ..., version: Optional[Any] = ...) -> None: ...
|
def set(self, key: Any, value: Any, timeout: Any = ..., version: Optional[Any] = ...) -> None: ...
|
||||||
def touch(self, key: Any, timeout: Any = ..., version: Optional[Any] = ...) -> None: ...
|
def touch(self, key: Any, timeout: Any = ..., version: Optional[Any] = ...) -> bool: ...
|
||||||
def delete(self, key: Any, version: Optional[Any] = ...) -> None: ...
|
def delete(self, key: Any, version: Optional[Any] = ...) -> None: ...
|
||||||
def get_many(self, keys: List[str], version: Optional[int] = ...) -> Dict[str, Union[int, str]]: ...
|
def get_many(self, keys: List[str], version: Optional[int] = ...) -> Dict[str, Union[int, str]]: ...
|
||||||
def get_or_set(
|
def get_or_set(
|
||||||
self, key: Any, default: Optional[Any], timeout: Any = ..., version: Optional[int] = ...
|
self, key: Any, default: Optional[Any], timeout: Any = ..., version: Optional[int] = ...
|
||||||
) -> Optional[Any]: ...
|
) -> Optional[Any]: ...
|
||||||
def has_key(self, key: Any, version: Optional[Any] = ...): ...
|
def has_key(self, key: Any, version: Optional[Any] = ...) -> bool: ...
|
||||||
def incr(self, key: str, delta: int = ..., version: Optional[int] = ...) -> int: ...
|
def incr(self, key: str, delta: int = ..., version: Optional[int] = ...) -> int: ...
|
||||||
def decr(self, key: str, delta: int = ..., version: Optional[int] = ...) -> int: ...
|
def decr(self, key: str, delta: int = ..., version: Optional[int] = ...) -> int: ...
|
||||||
def __contains__(self, key: str) -> bool: ...
|
def __contains__(self, key: str) -> bool: ...
|
||||||
|
|||||||
@@ -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
|
from . import model_checks as model_checks
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ ERROR: int
|
|||||||
CRITICAL: int
|
CRITICAL: int
|
||||||
|
|
||||||
class CheckMessage:
|
class CheckMessage:
|
||||||
level: Any = ...
|
level: int = ...
|
||||||
msg: Any = ...
|
msg: str = ...
|
||||||
hint: Any = ...
|
hint: Optional[str] = ...
|
||||||
obj: Any = ...
|
obj: Any = ...
|
||||||
id: Any = ...
|
id: Optional[str] = ...
|
||||||
def __init__(
|
def __init__(
|
||||||
self, level: int, msg: str, hint: Optional[str] = ..., obj: Any = ..., id: Optional[str] = ...
|
self, level: int, msg: str, hint: Optional[str] = ..., obj: Any = ..., id: Optional[str] = ...
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
@@ -25,19 +25,9 @@ class Info(CheckMessage):
|
|||||||
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
||||||
|
|
||||||
class Warning(CheckMessage):
|
class Warning(CheckMessage):
|
||||||
hint: str
|
|
||||||
id: str
|
|
||||||
level: int
|
|
||||||
msg: str
|
|
||||||
obj: Any
|
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
||||||
|
|
||||||
class Error(CheckMessage):
|
class Error(CheckMessage):
|
||||||
hint: None
|
|
||||||
id: str
|
|
||||||
level: int
|
|
||||||
msg: str
|
|
||||||
obj: Any
|
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
||||||
|
|
||||||
class Critical(CheckMessage):
|
class Critical(CheckMessage):
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ _T = TypeVar("_T", bound="File")
|
|||||||
|
|
||||||
class File(FileProxyMixin, IO[Any]):
|
class File(FileProxyMixin, IO[Any]):
|
||||||
DEFAULT_CHUNK_SIZE: Any = ...
|
DEFAULT_CHUNK_SIZE: Any = ...
|
||||||
file: StringIO = ...
|
file: IO[Any] = ...
|
||||||
name: str = ...
|
name: str = ...
|
||||||
mode: str = ...
|
mode: str = ...
|
||||||
def __init__(self, file: Any, name: Optional[str] = ...) -> None: ...
|
def __init__(self, file: Any, name: Optional[str] = ...) -> None: ...
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ class SkipFile(UploadFileException): ...
|
|||||||
class StopFutureHandlers(UploadFileException): ...
|
class StopFutureHandlers(UploadFileException): ...
|
||||||
|
|
||||||
class FileUploadHandler:
|
class FileUploadHandler:
|
||||||
chunk_size = ... # type: int
|
chunk_size: int = ...
|
||||||
file_name = ... # type: Optional[str]
|
file_name: Optional[str] = ...
|
||||||
content_type = ... # type: Optional[str]
|
content_type: Optional[str] = ...
|
||||||
content_length = ... # type: Optional[int]
|
content_length: Optional[int] = ...
|
||||||
charset = ... # type: Optional[str]
|
charset: Optional[str] = ...
|
||||||
content_type_extra = ... # type: Optional[Dict[str, str]]
|
content_type_extra: Optional[Dict[str, str]] = ...
|
||||||
request = ... # type: Optional[HttpRequest]
|
request: Optional[HttpRequest] = ...
|
||||||
field_name = ... # type: str
|
field_name: str = ...
|
||||||
def __init__(self, request: Optional[HttpRequest] = ...) -> None: ...
|
def __init__(self, request: Optional[HttpRequest] = ...) -> None: ...
|
||||||
def handle_raw_input(
|
def handle_raw_input(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Any, Callable, Dict, Optional, Union
|
from typing import Any, Callable, Dict, Optional, Union
|
||||||
|
|
||||||
from django.contrib.auth.models import AbstractUser
|
|
||||||
from django.contrib.sessions.backends.base import SessionBase
|
from django.contrib.sessions.backends.base import SessionBase
|
||||||
from django.http.response import HttpResponse
|
|
||||||
|
|
||||||
from django.core.handlers import base
|
from django.core.handlers import base
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
from django.http.response import HttpResponse
|
||||||
|
|
||||||
_Stream = Union[BytesIO, str]
|
_Stream = Union[BytesIO, str]
|
||||||
_WSGIEnviron = Dict[str, Any]
|
_WSGIEnviron = Dict[str, Any]
|
||||||
@@ -22,7 +20,6 @@ class LimitedStream:
|
|||||||
|
|
||||||
class WSGIRequest(HttpRequest):
|
class WSGIRequest(HttpRequest):
|
||||||
environ: _WSGIEnviron = ...
|
environ: _WSGIEnviron = ...
|
||||||
user: AbstractUser
|
|
||||||
session: SessionBase
|
session: SessionBase
|
||||||
encoding: Any = ...
|
encoding: Any = ...
|
||||||
def __init__(self, environ: _WSGIEnviron) -> None: ...
|
def __init__(self, environ: _WSGIEnviron) -> None: ...
|
||||||
|
|||||||
@@ -23,13 +23,16 @@ class OutputWrapper(TextIOBase):
|
|||||||
@property
|
@property
|
||||||
def style_func(self): ...
|
def style_func(self): ...
|
||||||
@style_func.setter
|
@style_func.setter
|
||||||
def style_func(self, style_func: Any): ...
|
def style_func(self, style_func: Callable[[str], str]): ...
|
||||||
ending: str = ...
|
ending: str = ...
|
||||||
def __init__(
|
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: ...
|
) -> None: ...
|
||||||
def __getattr__(self, name: str) -> Callable: ...
|
def __getattr__(self, name: str) -> Callable: ...
|
||||||
def isatty(self) -> bool: ...
|
def isatty(self) -> bool: ...
|
||||||
|
def write( # type: ignore[override]
|
||||||
|
self, msg: str, style_func: Optional[Callable[[str], str]] = ..., ending: Optional[str] = ...
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
class BaseCommand:
|
class BaseCommand:
|
||||||
help: str = ...
|
help: str = ...
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ class InvalidPage(Exception): ...
|
|||||||
class PageNotAnInteger(InvalidPage): ...
|
class PageNotAnInteger(InvalidPage): ...
|
||||||
class EmptyPage(InvalidPage): ...
|
class EmptyPage(InvalidPage): ...
|
||||||
|
|
||||||
class SupportsLen(Protocol):
|
class _SupportsLen(Protocol):
|
||||||
def __len__(self) -> int: ...
|
def __len__(self) -> int: ...
|
||||||
|
|
||||||
class SupportsCount(Protocol):
|
class _SupportsCount(Protocol):
|
||||||
def count(self) -> int: ...
|
def count(self) -> int: ...
|
||||||
|
|
||||||
class SupportsOrdered(Protocol):
|
class _SupportsOrdered(Protocol):
|
||||||
ordered: bool = ...
|
ordered: bool = ...
|
||||||
|
|
||||||
class Paginator:
|
class Paginator:
|
||||||
@@ -24,7 +24,7 @@ class Paginator:
|
|||||||
allow_empty_first_page: bool = ...
|
allow_empty_first_page: bool = ...
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
object_list: Union[SupportsLen, SupportsCount, SupportsOrdered],
|
object_list: Union[_SupportsLen, _SupportsCount, _SupportsOrdered],
|
||||||
per_page: Union[int, str],
|
per_page: Union[int, str],
|
||||||
orphans: int = ...,
|
orphans: int = ...,
|
||||||
allow_empty_first_page: bool = ...,
|
allow_empty_first_page: bool = ...,
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ def dumps(
|
|||||||
obj: Any, key: None = ..., salt: str = ..., serializer: Type[Serializer] = ..., compress: bool = ...
|
obj: Any, key: None = ..., salt: str = ..., serializer: Type[Serializer] = ..., compress: bool = ...
|
||||||
) -> str: ...
|
) -> str: ...
|
||||||
def loads(
|
def loads(
|
||||||
s: str, key: None = ..., salt: str = ..., serializer: Type[Serializer] = ..., max_age: Optional[int] = ...
|
s: str,
|
||||||
|
key: None = ...,
|
||||||
|
salt: str = ...,
|
||||||
|
serializer: Type[Serializer] = ...,
|
||||||
|
max_age: Optional[Union[int, timedelta]] = ...,
|
||||||
) -> Any: ...
|
) -> Any: ...
|
||||||
|
|
||||||
class Signer:
|
class Signer:
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ from .deletion import (
|
|||||||
DO_NOTHING as DO_NOTHING,
|
DO_NOTHING as DO_NOTHING,
|
||||||
PROTECT as PROTECT,
|
PROTECT as PROTECT,
|
||||||
SET as SET,
|
SET as SET,
|
||||||
|
RESTRICT as RESTRICT,
|
||||||
|
ProtectedError as ProtectedError,
|
||||||
|
RestrictedError as RestrictedError,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .query import (
|
from .query import (
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ from django.db.models.options import Options
|
|||||||
|
|
||||||
_Self = TypeVar("_Self", bound="Model")
|
_Self = TypeVar("_Self", bound="Model")
|
||||||
|
|
||||||
|
class ModelStateFieldsCacheDescriptor: ...
|
||||||
|
|
||||||
|
class ModelState:
|
||||||
|
db: Optional[str] = ...
|
||||||
|
adding: bool = ...
|
||||||
|
fields_cache: ModelStateFieldsCacheDescriptor = ...
|
||||||
|
|
||||||
class ModelBase(type): ...
|
class ModelBase(type): ...
|
||||||
|
|
||||||
class Model(metaclass=ModelBase):
|
class Model(metaclass=ModelBase):
|
||||||
@@ -17,7 +24,12 @@ class Model(metaclass=ModelBase):
|
|||||||
_default_manager: BaseManager[Model]
|
_default_manager: BaseManager[Model]
|
||||||
objects: BaseManager[Any]
|
objects: BaseManager[Any]
|
||||||
pk: Any = ...
|
pk: Any = ...
|
||||||
|
_state: ModelState
|
||||||
def __init__(self: _Self, *args, **kwargs) -> None: ...
|
def __init__(self: _Self, *args, **kwargs) -> None: ...
|
||||||
|
@classmethod
|
||||||
|
def add_to_class(cls, name: str, value: Any): ...
|
||||||
|
@classmethod
|
||||||
|
def from_db(cls, db: Optional[str], field_names: Collection[str], values: Collection[Any]) -> _Self: ...
|
||||||
def delete(self, using: Any = ..., keep_parents: bool = ...) -> Tuple[int, Dict[str, int]]: ...
|
def delete(self, using: Any = ..., keep_parents: bool = ...) -> Tuple[int, Dict[str, int]]: ...
|
||||||
def full_clean(self, exclude: Optional[Collection[str]] = ..., validate_unique: bool = ...) -> None: ...
|
def full_clean(self, exclude: Optional[Collection[str]] = ..., validate_unique: bool = ...) -> None: ...
|
||||||
def clean(self) -> None: ...
|
def clean(self) -> None: ...
|
||||||
@@ -46,10 +58,3 @@ class Model(metaclass=ModelBase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def check(cls, **kwargs: Any) -> List[CheckMessage]: ...
|
def check(cls, **kwargs: Any) -> List[CheckMessage]: ...
|
||||||
def __getstate__(self) -> dict: ...
|
def __getstate__(self) -> dict: ...
|
||||||
|
|
||||||
class ModelStateFieldsCacheDescriptor: ...
|
|
||||||
|
|
||||||
class ModelState:
|
|
||||||
db: None = ...
|
|
||||||
adding: bool = ...
|
|
||||||
fields_cache: ModelStateFieldsCacheDescriptor = ...
|
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ def SET_NULL(collector, field, sub_objs, using): ...
|
|||||||
def SET_DEFAULT(collector, field, sub_objs, using): ...
|
def SET_DEFAULT(collector, field, sub_objs, using): ...
|
||||||
def DO_NOTHING(collector, field, sub_objs, using): ...
|
def DO_NOTHING(collector, field, sub_objs, using): ...
|
||||||
def PROTECT(collector, field, sub_objs, using): ...
|
def PROTECT(collector, field, sub_objs, using): ...
|
||||||
|
def RESTRICT(collector, field, sub_objs, using): ...
|
||||||
def SET(value: Any) -> Callable: ...
|
def SET(value: Any) -> Callable: ...
|
||||||
def get_candidate_relations_to_delete(opts: Options) -> Iterable[Field]: ...
|
def get_candidate_relations_to_delete(opts: Options) -> Iterable[Field]: ...
|
||||||
|
|
||||||
class ProtectedError(IntegrityError): ...
|
class ProtectedError(IntegrityError): ...
|
||||||
|
class RestrictedError(IntegrityError): ...
|
||||||
|
|
||||||
class Collector:
|
class Collector:
|
||||||
def __init__(self, using: str) -> None: ...
|
def __init__(self, using: str) -> None: ...
|
||||||
|
|||||||
@@ -42,9 +42,6 @@ _ST = TypeVar("_ST")
|
|||||||
# __get__ return type
|
# __get__ return type
|
||||||
_GT = TypeVar("_GT")
|
_GT = TypeVar("_GT")
|
||||||
|
|
||||||
class CharField(Field[str, str]):
|
|
||||||
|
|
||||||
|
|
||||||
class Field(RegisterLookupMixin, Generic[_ST, _GT]):
|
class Field(RegisterLookupMixin, Generic[_ST, _GT]):
|
||||||
_pyi_private_set_type: Any
|
_pyi_private_set_type: Any
|
||||||
_pyi_private_get_type: Any
|
_pyi_private_get_type: Any
|
||||||
@@ -111,7 +108,10 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
|
|||||||
def set_attributes_from_name(self, name: str) -> None: ...
|
def set_attributes_from_name(self, name: str) -> None: ...
|
||||||
def db_type(self, connection: Any) -> str: ...
|
def db_type(self, connection: Any) -> str: ...
|
||||||
def db_parameters(self, connection: Any) -> Dict[str, str]: ...
|
def db_parameters(self, connection: Any) -> Dict[str, str]: ...
|
||||||
|
def pre_save(self, model_instance: Model, add: bool) -> Any: ...
|
||||||
def get_prep_value(self, value: Any) -> Any: ...
|
def get_prep_value(self, value: Any) -> Any: ...
|
||||||
|
def get_db_prep_value(self, value: Any, connection: Any, prepared: bool) -> Any: ...
|
||||||
|
def get_db_prep_save(self, value: Any, connection: Any) -> Any: ...
|
||||||
def get_internal_type(self) -> str: ...
|
def get_internal_type(self) -> str: ...
|
||||||
# TODO: plugin support
|
# TODO: plugin support
|
||||||
def formfield(self, **kwargs) -> Any: ...
|
def formfield(self, **kwargs) -> Any: ...
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class BaseManager(QuerySet[_T]):
|
|||||||
name: str = ...
|
name: str = ...
|
||||||
model: Type[_T] = ...
|
model: Type[_T] = ...
|
||||||
db: str
|
db: str
|
||||||
|
_db: Optional[str]
|
||||||
def __init__(self) -> None: ...
|
def __init__(self) -> None: ...
|
||||||
def deconstruct(self) -> Tuple[bool, str, None, Tuple, Dict[str, int]]: ...
|
def deconstruct(self) -> Tuple[bool, str, None, Tuple, Dict[str, int]]: ...
|
||||||
def check(self, **kwargs: Any) -> List[Any]: ...
|
def check(self, **kwargs: Any) -> List[Any]: ...
|
||||||
|
|||||||
@@ -39,4 +39,11 @@ def atomic(using: _C) -> _C: ...
|
|||||||
# Decorator or context-manager with parameters
|
# Decorator or context-manager with parameters
|
||||||
@overload
|
@overload
|
||||||
def atomic(using: Optional[str] = ..., savepoint: bool = ...) -> Atomic: ...
|
def atomic(using: Optional[str] = ..., savepoint: bool = ...) -> Atomic: ...
|
||||||
def non_atomic_requests(using: Callable = ...) -> Callable: ...
|
|
||||||
|
# Bare decorator
|
||||||
|
@overload
|
||||||
|
def non_atomic_requests(using: _C) -> _C: ...
|
||||||
|
|
||||||
|
# Decorator with arguments
|
||||||
|
@overload
|
||||||
|
def non_atomic_requests(using: Optional[str] = ...) -> Callable[[_C], _C]: ...
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class Field:
|
|||||||
initial: Any
|
initial: Any
|
||||||
label: Optional[str]
|
label: Optional[str]
|
||||||
required: bool
|
required: bool
|
||||||
widget: Widget = ...
|
widget: Type[Widget] = ...
|
||||||
hidden_widget: Any = ...
|
hidden_widget: Any = ...
|
||||||
default_validators: Any = ...
|
default_validators: Any = ...
|
||||||
default_error_messages: Any = ...
|
default_error_messages: Any = ...
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, Type, Union
|
from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, Type, Union
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError as ValidationError
|
from django.core.exceptions import ValidationError as ValidationError
|
||||||
|
from django.core.files import uploadedfile
|
||||||
from django.forms.boundfield import BoundField
|
from django.forms.boundfield import BoundField
|
||||||
from django.forms.fields import Field
|
from django.forms.fields import Field
|
||||||
from django.forms.renderers import BaseRenderer
|
from django.forms.renderers import BaseRenderer
|
||||||
from django.forms.utils import ErrorDict, ErrorList
|
from django.forms.utils import ErrorDict, ErrorList
|
||||||
from django.forms.widgets import Media, MediaDefiningClass
|
from django.forms.widgets import Media, MediaDefiningClass
|
||||||
|
from django.utils.datastructures import MultiValueDict
|
||||||
from django.utils.safestring import SafeText
|
from django.utils.safestring import SafeText
|
||||||
|
|
||||||
class DeclarativeFieldsMetaclass(MediaDefiningClass): ...
|
class DeclarativeFieldsMetaclass(MediaDefiningClass): ...
|
||||||
@@ -18,11 +20,11 @@ class BaseForm:
|
|||||||
use_required_attribute: bool = ...
|
use_required_attribute: bool = ...
|
||||||
is_bound: bool = ...
|
is_bound: bool = ...
|
||||||
data: Dict[str, Any] = ...
|
data: Dict[str, Any] = ...
|
||||||
files: Optional[Dict[str, Any]] = ...
|
files: MultiValueDict[str, uploadedfile.UploadedFile] = ...
|
||||||
auto_id: str = ...
|
auto_id: Union[bool, str] = ...
|
||||||
initial: Dict[str, Any] = ...
|
initial: Dict[str, Any] = ...
|
||||||
error_class: Type[ErrorList] = ...
|
error_class: Type[ErrorList] = ...
|
||||||
prefix: str = ...
|
prefix: Optional[str] = ...
|
||||||
label_suffix: str = ...
|
label_suffix: str = ...
|
||||||
empty_permitted: bool = ...
|
empty_permitted: bool = ...
|
||||||
fields: Dict[str, Any] = ...
|
fields: Dict[str, Any] = ...
|
||||||
@@ -67,6 +69,9 @@ class BaseForm:
|
|||||||
def hidden_fields(self): ...
|
def hidden_fields(self): ...
|
||||||
def visible_fields(self): ...
|
def visible_fields(self): ...
|
||||||
def get_initial_for_field(self, field: Field, field_name: str) -> Any: ...
|
def get_initial_for_field(self, field: Field, field_name: str) -> Any: ...
|
||||||
|
def _html_output(
|
||||||
|
self, normal_row: str, error_row: str, row_ender: str, help_text_html: str, errors_on_separate_row: bool,
|
||||||
|
) -> SafeText: ...
|
||||||
|
|
||||||
class Form(BaseForm):
|
class Form(BaseForm):
|
||||||
base_fields: Dict[str, Field]
|
base_fields: Dict[str, Field]
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from typing import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from django.contrib.auth.base_user import AbstractBaseUser
|
from django.contrib.auth.base_user import AbstractBaseUser
|
||||||
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.contrib.sessions.backends.base import SessionBase
|
from django.contrib.sessions.backends.base import SessionBase
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.utils.datastructures import CaseInsensitiveMapping, ImmutableList, MultiValueDict
|
from django.utils.datastructures import CaseInsensitiveMapping, ImmutableList, MultiValueDict
|
||||||
@@ -51,7 +52,7 @@ class HttpRequest(BytesIO):
|
|||||||
resolver_match: ResolverMatch = ...
|
resolver_match: ResolverMatch = ...
|
||||||
content_type: Optional[str] = ...
|
content_type: Optional[str] = ...
|
||||||
content_params: Optional[Dict[str, str]] = ...
|
content_params: Optional[Dict[str, str]] = ...
|
||||||
user: AbstractBaseUser
|
user: Union[AbstractBaseUser, AnonymousUser]
|
||||||
site: Site
|
site: Site
|
||||||
session: SessionBase
|
session: SessionBase
|
||||||
encoding: Optional[str] = ...
|
encoding: Optional[str] = ...
|
||||||
|
|||||||
@@ -62,25 +62,27 @@ class HttpResponseBase(Iterable[Any]):
|
|||||||
def __iter__(self) -> Iterator[Any]: ...
|
def __iter__(self) -> Iterator[Any]: ...
|
||||||
|
|
||||||
class HttpResponse(HttpResponseBase):
|
class HttpResponse(HttpResponseBase):
|
||||||
client: Client
|
|
||||||
context: Context
|
|
||||||
content: Any
|
content: Any
|
||||||
csrf_cookie_set: bool
|
csrf_cookie_set: bool
|
||||||
redirect_chain: List[Tuple[str, int]]
|
redirect_chain: List[Tuple[str, int]]
|
||||||
request: Dict[str, Any]
|
|
||||||
resolver_match: ResolverMatch
|
|
||||||
sameorigin: bool
|
sameorigin: bool
|
||||||
templates: List[Template]
|
|
||||||
test_server_port: str
|
test_server_port: str
|
||||||
test_was_secure_request: bool
|
test_was_secure_request: bool
|
||||||
wsgi_request: WSGIRequest
|
|
||||||
xframe_options_exempt: bool
|
xframe_options_exempt: bool
|
||||||
streaming: bool = ...
|
streaming: bool = ...
|
||||||
def __init__(self, content: object = ..., *args: Any, **kwargs: Any) -> None: ...
|
def __init__(self, content: object = ..., *args: Any, **kwargs: Any) -> None: ...
|
||||||
def serialize(self) -> bytes: ...
|
def serialize(self) -> bytes: ...
|
||||||
@property
|
@property
|
||||||
def url(self) -> str: ...
|
def url(self) -> str: ...
|
||||||
def json(self) -> Dict[str, Any]: ...
|
# Attributes assigned by monkey-patching in test client ClientHandler.__call__()
|
||||||
|
wsgi_request: WSGIRequest
|
||||||
|
# Attributes assigned by monkey-patching in test client Client.request()
|
||||||
|
client: Client
|
||||||
|
request: Dict[str, Any]
|
||||||
|
templates: List[Template]
|
||||||
|
context: Context
|
||||||
|
resolver_match: ResolverMatch
|
||||||
|
def json(self) -> Any: ...
|
||||||
|
|
||||||
class StreamingHttpResponse(HttpResponseBase):
|
class StreamingHttpResponse(HttpResponseBase):
|
||||||
content: Any
|
content: Any
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from typing import Any, Callable, Dict, List, Optional, Protocol, Sequence, Type, TypeVar, Union
|
import sys
|
||||||
|
from typing import Any, Callable, List, Mapping, Optional, overload, Protocol, Sequence, Type, TypeVar, Union
|
||||||
|
|
||||||
from django.db.models.base import Model
|
from django.db.models.base import Model
|
||||||
from django.http.response import (
|
from django.http.response import (
|
||||||
@@ -10,9 +11,14 @@ from django.http.response import (
|
|||||||
from django.db.models import Manager, QuerySet
|
from django.db.models import Manager, QuerySet
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
|
||||||
|
if sys.version_info < (3, 8):
|
||||||
|
from typing_extensions import Literal
|
||||||
|
else:
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
def render_to_response(
|
def render_to_response(
|
||||||
template_name: Union[str, Sequence[str]],
|
template_name: Union[str, Sequence[str]],
|
||||||
context: Optional[Dict[str, Any]] = ...,
|
context: Optional[Mapping[str, Any]] = ...,
|
||||||
content_type: Optional[str] = ...,
|
content_type: Optional[str] = ...,
|
||||||
status: Optional[int] = ...,
|
status: Optional[int] = ...,
|
||||||
using: Optional[str] = ...,
|
using: Optional[str] = ...,
|
||||||
@@ -20,7 +26,7 @@ def render_to_response(
|
|||||||
def render(
|
def render(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
template_name: Union[str, Sequence[str]],
|
template_name: Union[str, Sequence[str]],
|
||||||
context: Optional[Dict[str, Any]] = ...,
|
context: Optional[Mapping[str, Any]] = ...,
|
||||||
content_type: Optional[str] = ...,
|
content_type: Optional[str] = ...,
|
||||||
status: Optional[int] = ...,
|
status: Optional[int] = ...,
|
||||||
using: Optional[str] = ...,
|
using: Optional[str] = ...,
|
||||||
@@ -28,6 +34,15 @@ def render(
|
|||||||
|
|
||||||
class SupportsGetAbsoluteUrl(Protocol): ...
|
class SupportsGetAbsoluteUrl(Protocol): ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def redirect(
|
||||||
|
to: Union[Callable, str, SupportsGetAbsoluteUrl], *args: Any, permanent: Literal[True], **kwargs: Any
|
||||||
|
) -> HttpResponsePermanentRedirect: ...
|
||||||
|
@overload
|
||||||
|
def redirect(
|
||||||
|
to: Union[Callable, str, SupportsGetAbsoluteUrl], *args: Any, permanent: Literal[False], **kwargs: Any
|
||||||
|
) -> HttpResponseRedirect: ...
|
||||||
|
@overload
|
||||||
def redirect(
|
def redirect(
|
||||||
to: Union[Callable, str, SupportsGetAbsoluteUrl], *args: Any, permanent: bool = ..., **kwargs: Any
|
to: Union[Callable, str, SupportsGetAbsoluteUrl], *args: Any, permanent: bool = ..., **kwargs: Any
|
||||||
) -> Union[HttpResponseRedirect, HttpResponsePermanentRedirect]: ...
|
) -> Union[HttpResponseRedirect, HttpResponsePermanentRedirect]: ...
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from types import TracebackType
|
||||||
from typing import Any, Dict, List, Optional, Pattern, Tuple, Type, Union
|
from typing import Any, Dict, List, Optional, Pattern, Tuple, Type, Union
|
||||||
|
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.contrib.sessions.backends.base import SessionBase
|
from django.contrib.sessions.backends.base import SessionBase
|
||||||
from django.core.handlers.base import BaseHandler
|
from django.core.handlers.base import BaseHandler
|
||||||
from django.core.serializers.json import DjangoJSONEncoder
|
|
||||||
from django.http.cookie import SimpleCookie
|
from django.http.cookie import SimpleCookie
|
||||||
from django.http.request import HttpRequest
|
from django.http.request import HttpRequest
|
||||||
from django.http.response import HttpResponse, HttpResponseBase
|
from django.http.response import HttpResponse, HttpResponseBase
|
||||||
|
|
||||||
from django.core.handlers.wsgi import WSGIRequest
|
from django.core.handlers.wsgi import WSGIRequest
|
||||||
|
from json import JSONEncoder
|
||||||
|
|
||||||
BOUNDARY: str = ...
|
BOUNDARY: str = ...
|
||||||
MULTIPART_CONTENT: str = ...
|
MULTIPART_CONTENT: str = ...
|
||||||
@@ -37,11 +38,11 @@ def encode_multipart(boundary: str, data: Dict[str, Any]) -> bytes: ...
|
|||||||
def encode_file(boundary: str, key: str, file: Any) -> List[bytes]: ...
|
def encode_file(boundary: str, key: str, file: Any) -> List[bytes]: ...
|
||||||
|
|
||||||
class RequestFactory:
|
class RequestFactory:
|
||||||
json_encoder: Type[DjangoJSONEncoder] = ...
|
json_encoder: Type[JSONEncoder]
|
||||||
defaults: Dict[str, str] = ...
|
defaults: Dict[str, str]
|
||||||
cookies: SimpleCookie = ...
|
cookies: SimpleCookie
|
||||||
errors: BytesIO = ...
|
errors: BytesIO
|
||||||
def __init__(self, *, json_encoder: Any = ..., **defaults: Any) -> None: ...
|
def __init__(self, *, json_encoder: Type[JSONEncoder] = ..., **defaults: Any) -> None: ...
|
||||||
def request(self, **request: Any) -> WSGIRequest: ...
|
def request(self, **request: Any) -> WSGIRequest: ...
|
||||||
def get(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> WSGIRequest: ...
|
def get(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> WSGIRequest: ...
|
||||||
def post(
|
def post(
|
||||||
@@ -54,6 +55,7 @@ class RequestFactory:
|
|||||||
path: str,
|
path: str,
|
||||||
data: Union[Dict[str, str], str] = ...,
|
data: Union[Dict[str, str], str] = ...,
|
||||||
content_type: str = ...,
|
content_type: str = ...,
|
||||||
|
follow: bool = ...,
|
||||||
secure: bool = ...,
|
secure: bool = ...,
|
||||||
**extra: Any
|
**extra: Any
|
||||||
) -> WSGIRequest: ...
|
) -> WSGIRequest: ...
|
||||||
@@ -76,39 +78,50 @@ class RequestFactory:
|
|||||||
**extra: Any
|
**extra: Any
|
||||||
) -> WSGIRequest: ...
|
) -> WSGIRequest: ...
|
||||||
|
|
||||||
class Client:
|
class Client(RequestFactory):
|
||||||
json_encoder: Type[DjangoJSONEncoder] = ...
|
handler: ClientHandler
|
||||||
defaults: Dict[str, str] = ...
|
raise_request_exception: bool
|
||||||
cookies: SimpleCookie = ...
|
exc_info: Optional[Tuple[Type[BaseException], BaseException, TracebackType]]
|
||||||
errors: BytesIO = ...
|
def __init__(
|
||||||
handler: ClientHandler = ...
|
self,
|
||||||
exc_info: None = ...
|
enforce_csrf_checks: bool = ...,
|
||||||
def __init__(self, enforce_csrf_checks: bool = ..., **defaults: Any) -> None: ...
|
raise_request_exception: bool = ...,
|
||||||
def request(self, **request: Any) -> Any: ...
|
*,
|
||||||
def get(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
json_encoder: Type[JSONEncoder] = ...,
|
||||||
def post(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
**defaults: Any
|
||||||
def head(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
) -> None: ...
|
||||||
def trace(self, path: str, secure: bool = ..., **extra: Any) -> Any: ...
|
# Silence type warnings, since this class overrides arguments and return types in an unsafe manner.
|
||||||
def options(
|
def request(self, **request: Any) -> HttpResponse: ... # type: ignore
|
||||||
|
def get( # type: ignore
|
||||||
|
self, path: str, data: Any = ..., follow: bool = ..., secure: bool = ..., **extra: Any
|
||||||
|
) -> HttpResponse: ... # type: ignore
|
||||||
|
def post( # type: ignore
|
||||||
|
self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
|
||||||
|
) -> HttpResponse: ... # type: ignore
|
||||||
|
def head( # type: ignore
|
||||||
|
self, path: str, data: Any = ..., follow: bool = ..., secure: bool = ..., **extra: Any
|
||||||
|
) -> HttpResponse: ... # type: ignore
|
||||||
|
def trace( # type: ignore
|
||||||
|
self, path: str, follow: bool = ..., secure: bool = ..., **extra: Any
|
||||||
|
) -> HttpResponse: ... # type: ignore
|
||||||
|
def options( # type: ignore
|
||||||
self,
|
self,
|
||||||
path: str,
|
path: str,
|
||||||
data: Union[Dict[str, str], str] = ...,
|
data: Union[Dict[str, str], str] = ...,
|
||||||
content_type: str = ...,
|
content_type: str = ...,
|
||||||
|
follow: bool = ...,
|
||||||
secure: bool = ...,
|
secure: bool = ...,
|
||||||
**extra: Any
|
**extra: Any
|
||||||
) -> Any: ...
|
) -> HttpResponse: ... # type: ignore
|
||||||
def put(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
def put( # type: ignore
|
||||||
def patch(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
|
||||||
def delete(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
) -> HttpResponse: ... # type: ignore
|
||||||
def generic(
|
def patch( # type: ignore
|
||||||
self,
|
self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
|
||||||
method: str,
|
) -> HttpResponse: ... # type: ignore
|
||||||
path: str,
|
def delete( # type: ignore
|
||||||
data: Any = ...,
|
self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
|
||||||
content_type: Optional[str] = ...,
|
) -> HttpResponse: ... # type: ignore
|
||||||
secure: bool = ...,
|
|
||||||
**extra: Any
|
|
||||||
) -> Any: ...
|
|
||||||
def store_exc_info(self, **kwargs: Any) -> None: ...
|
def store_exc_info(self, **kwargs: Any) -> None: ...
|
||||||
@property
|
@property
|
||||||
def session(self) -> SessionBase: ...
|
def session(self) -> SessionBase: ...
|
||||||
|
|||||||
@@ -126,9 +126,17 @@ class SimpleTestCase(unittest.TestCase):
|
|||||||
self, needle: str, haystack: SafeText, count: Optional[int] = ..., msg_prefix: str = ...
|
self, needle: str, haystack: SafeText, count: Optional[int] = ..., msg_prefix: str = ...
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def assertJSONEqual(
|
def assertJSONEqual(
|
||||||
self, raw: str, expected_data: Union[Dict[str, str], bool, str], msg: Optional[str] = ...
|
self,
|
||||||
|
raw: str,
|
||||||
|
expected_data: Union[Dict[str, Any], List[Any], str, int, float, bool, None],
|
||||||
|
msg: Optional[str] = ...,
|
||||||
|
) -> None: ...
|
||||||
|
def assertJSONNotEqual(
|
||||||
|
self,
|
||||||
|
raw: str,
|
||||||
|
expected_data: Union[Dict[str, Any], List[Any], str, int, float, bool, None],
|
||||||
|
msg: Optional[str] = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def assertJSONNotEqual(self, raw: str, expected_data: str, msg: Optional[str] = ...) -> None: ...
|
|
||||||
def assertXMLEqual(self, xml1: str, xml2: str, msg: Optional[str] = ...) -> None: ...
|
def assertXMLEqual(self, xml1: str, xml2: str, msg: Optional[str] = ...) -> None: ...
|
||||||
def assertXMLNotEqual(self, xml1: str, xml2: str, msg: Optional[str] = ...) -> None: ...
|
def assertXMLNotEqual(self, xml1: str, xml2: str, msg: Optional[str] = ...) -> None: ...
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from typing import (
|
|||||||
Type,
|
Type,
|
||||||
Union,
|
Union,
|
||||||
ContextManager,
|
ContextManager,
|
||||||
|
TypeVar,
|
||||||
)
|
)
|
||||||
|
|
||||||
from django.apps.registry import Apps
|
from django.apps.registry import Apps
|
||||||
@@ -29,6 +30,7 @@ from django.conf import LazySettings, Settings
|
|||||||
|
|
||||||
_TestClass = Type[SimpleTestCase]
|
_TestClass = Type[SimpleTestCase]
|
||||||
_DecoratedTest = Union[Callable, _TestClass]
|
_DecoratedTest = Union[Callable, _TestClass]
|
||||||
|
_C = TypeVar("_C", bound=Callable) # Any callable
|
||||||
|
|
||||||
TZ_SUPPORT: bool = ...
|
TZ_SUPPORT: bool = ...
|
||||||
|
|
||||||
@@ -56,7 +58,7 @@ class TestContextDecorator:
|
|||||||
def __enter__(self) -> Optional[Apps]: ...
|
def __enter__(self) -> Optional[Apps]: ...
|
||||||
def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None: ...
|
def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None: ...
|
||||||
def decorate_class(self, cls: _TestClass) -> _TestClass: ...
|
def decorate_class(self, cls: _TestClass) -> _TestClass: ...
|
||||||
def decorate_callable(self, func: Callable) -> Callable: ...
|
def decorate_callable(self, func: _C) -> _C: ...
|
||||||
def __call__(self, decorated: _DecoratedTest) -> Any: ...
|
def __call__(self, decorated: _DecoratedTest) -> Any: ...
|
||||||
|
|
||||||
class override_settings(TestContextDecorator):
|
class override_settings(TestContextDecorator):
|
||||||
@@ -146,7 +148,7 @@ def get_unique_databases_and_mirrors() -> Tuple[Dict[_Signature, _TestDatabase],
|
|||||||
def teardown_databases(
|
def teardown_databases(
|
||||||
old_config: Iterable[Tuple[Any, str, bool]], verbosity: int, parallel: int = ..., keepdb: bool = ...
|
old_config: Iterable[Tuple[Any, str, bool]], verbosity: int, parallel: int = ..., keepdb: bool = ...
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def require_jinja2(test_func: Callable) -> Callable: ...
|
def require_jinja2(test_func: _C) -> _C: ...
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def register_lookup(
|
def register_lookup(
|
||||||
field: Type[RegisterLookupMixin], *lookups: Type[Union[Lookup, Transform]], lookup_name: Optional[str] = ...
|
field: Type[RegisterLookupMixin], *lookups: Type[Union[Lookup, Transform]], lookup_name: Optional[str] = ...
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from typing import Any, Optional, Tuple
|
from typing import Any, Optional, Tuple
|
||||||
|
|
||||||
from django.core.cache.backends.base import BaseCache
|
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.core.handlers.wsgi import WSGIRequest
|
||||||
from django.http.response import HttpResponse, HttpResponseBase
|
from django.http.response import HttpResponse, HttpResponseBase
|
||||||
|
|
||||||
@@ -28,5 +27,5 @@ def learn_cache_key(
|
|||||||
response: HttpResponse,
|
response: HttpResponse,
|
||||||
cache_timeout: Optional[float] = ...,
|
cache_timeout: Optional[float] = ...,
|
||||||
key_prefix: Optional[str] = ...,
|
key_prefix: Optional[str] = ...,
|
||||||
cache: Optional[LocMemCache] = ...,
|
cache: Optional[BaseCache] = ...,
|
||||||
) -> str: ...
|
) -> str: ...
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ _V = TypeVar("_V")
|
|||||||
|
|
||||||
class OrderedSet(MutableSet[_K]):
|
class OrderedSet(MutableSet[_K]):
|
||||||
dict: Dict[_K, None] = ...
|
dict: Dict[_K, None] = ...
|
||||||
|
def __init__(self, iterable: Optional[Iterable[_K]] = ...) -> None: ...
|
||||||
def __contains__(self, item: object) -> bool: ...
|
def __contains__(self, item: object) -> bool: ...
|
||||||
def __iter__(self) -> Iterator[_K]: ...
|
def __iter__(self) -> Iterator[_K]: ...
|
||||||
def __len__(self) -> int: ...
|
def __len__(self) -> int: ...
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
from typing import Any, Callable, Iterable, Optional, Type, Union
|
from typing import Any, Callable, Iterable, Optional, Type, Union, TypeVar
|
||||||
|
|
||||||
from django.utils.deprecation import MiddlewareMixin
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
|
from django.views.generic.base import View
|
||||||
|
|
||||||
|
_T = TypeVar("_T", bound=Union[View, Callable]) # Any callable
|
||||||
|
|
||||||
class classonlymethod(classmethod): ...
|
class classonlymethod(classmethod): ...
|
||||||
|
|
||||||
def method_decorator(decorator: Union[Callable, Iterable[Callable]], name: str = ...) -> Callable: ...
|
def method_decorator(decorator: Union[Callable, Iterable[Callable]], name: str = ...) -> Callable[[_T], _T]: ...
|
||||||
def decorator_from_middleware_with_args(middleware_class: type) -> Callable: ...
|
def decorator_from_middleware_with_args(middleware_class: type) -> Callable: ...
|
||||||
def decorator_from_middleware(middleware_class: type) -> Callable: ...
|
def decorator_from_middleware(middleware_class: type) -> Callable: ...
|
||||||
def available_attrs(fn: Any): ...
|
def available_attrs(fn: Callable): ...
|
||||||
def make_middleware_decorator(middleware_class: Type[MiddlewareMixin]) -> Callable: ...
|
def make_middleware_decorator(middleware_class: Type[MiddlewareMixin]) -> Callable: ...
|
||||||
|
|
||||||
class classproperty:
|
class classproperty:
|
||||||
|
|||||||
@@ -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 functools import wraps as wraps # noqa: F401
|
||||||
|
|
||||||
from django.db.models.base import Model
|
from django.db.models.base import Model
|
||||||
|
|
||||||
def curry(_curried_func: Any, *args: Any, **kwargs: Any): ...
|
def curry(_curried_func: Any, *args: Any, **kwargs: Any): ...
|
||||||
|
|
||||||
class cached_property:
|
_T = TypeVar("_T")
|
||||||
func: Callable = ...
|
|
||||||
|
class cached_property(Generic[_T]):
|
||||||
|
func: Callable[..., _T] = ...
|
||||||
__doc__: Any = ...
|
__doc__: Any = ...
|
||||||
name: str = ...
|
name: str = ...
|
||||||
def __init__(self, func: Callable, name: Optional[str] = ...) -> None: ...
|
def __init__(self, func: Callable[..., _T], name: Optional[str] = ...): ...
|
||||||
def __get__(self, instance: Any, cls: Type[Any] = ...) -> Any: ...
|
@overload
|
||||||
|
def __get__(self, instance: None, cls: Type[Any] = ...) -> "cached_property[_T]": ...
|
||||||
|
@overload
|
||||||
|
def __get__(self, instance: object, cls: Type[Any] = ...) -> _T: ...
|
||||||
|
|
||||||
class Promise: ...
|
class Promise: ...
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,6 @@ NOCOLOR_PALETTE: str
|
|||||||
DARK_PALETTE: str
|
DARK_PALETTE: str
|
||||||
LIGHT_PALETTE: str
|
LIGHT_PALETTE: str
|
||||||
PALETTES: Any
|
PALETTES: Any
|
||||||
DEFAULT_PALETTE = DARK_PALETTE
|
DEFAULT_PALETTE: str = ...
|
||||||
|
|
||||||
def parse_color_setting(config_string: str) -> Optional[Dict[str, Dict[str, Union[Tuple[str], str]]]]: ...
|
def parse_color_setting(config_string: str) -> Optional[Dict[str, Dict[str, Union[Tuple[str], str]]]]: ...
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ def to_locale(language: str) -> str: ...
|
|||||||
def get_language_from_request(request: WSGIRequest, check_path: bool = ...) -> str: ...
|
def get_language_from_request(request: WSGIRequest, check_path: bool = ...) -> str: ...
|
||||||
def templatize(src: str, **kwargs: Any) -> str: ...
|
def templatize(src: str, **kwargs: Any) -> str: ...
|
||||||
def deactivate_all() -> None: ...
|
def deactivate_all() -> None: ...
|
||||||
|
def get_supported_language_variant(lang_code: str, strict: bool = ...) -> str: ...
|
||||||
def get_language_info(lang_code: str) -> Any: ...
|
def get_language_info(lang_code: str) -> Any: ...
|
||||||
|
|
||||||
from . import trans_real as trans_real
|
from . import trans_real as trans_real
|
||||||
|
|||||||
@@ -10,3 +10,5 @@ select = F401, Y
|
|||||||
max_line_length = 120
|
max_line_length = 120
|
||||||
per-file-ignores =
|
per-file-ignores =
|
||||||
*__init__.pyi: F401
|
*__init__.pyi: F401
|
||||||
|
base_user.pyi: Y003
|
||||||
|
models.pyi: Y003
|
||||||
|
|||||||
113
mfile.py
113
mfile.py
@@ -1,113 +0,0 @@
|
|||||||
from graphviz import Digraph
|
|
||||||
from mypy.options import Options
|
|
||||||
|
|
||||||
source = """
|
|
||||||
from root.package import MyQuerySet
|
|
||||||
|
|
||||||
MyQuerySet().mymethod()
|
|
||||||
"""
|
|
||||||
|
|
||||||
from mypy import parse
|
|
||||||
|
|
||||||
parsed = parse.parse(source, 'myfile.py', None, None, Options())
|
|
||||||
print(parsed)
|
|
||||||
|
|
||||||
graphattrs = {
|
|
||||||
"labelloc": "t",
|
|
||||||
"fontcolor": "blue",
|
|
||||||
# "bgcolor": "#333333",
|
|
||||||
"margin": "0",
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeattrs = {
|
|
||||||
# "color": "white",
|
|
||||||
"fontcolor": "#00008b",
|
|
||||||
# "style": "filled",
|
|
||||||
# "fillcolor": "#ffffff",
|
|
||||||
# "fillcolor": "#006699",
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeattrs = {
|
|
||||||
# "color": "white",
|
|
||||||
# "fontcolor": "white",
|
|
||||||
}
|
|
||||||
|
|
||||||
graph = Digraph('mfile.py', graph_attr=graphattrs, node_attr=nodeattrs, edge_attr=edgeattrs)
|
|
||||||
graph.node('__builtins__')
|
|
||||||
|
|
||||||
graph.node('django.db.models')
|
|
||||||
graph.node('django.db.models.fields')
|
|
||||||
|
|
||||||
graph.edge('django.db.models', 'django.db.models.fields')
|
|
||||||
graph.edge('django.db.models', '__builtins__')
|
|
||||||
graph.edge('django.db.models.fields', '__builtins__')
|
|
||||||
|
|
||||||
graph.node('mymodule')
|
|
||||||
graph.edge('mymodule', 'django.db.models')
|
|
||||||
graph.edge('mymodule', '__builtins__')
|
|
||||||
#
|
|
||||||
# graph.node('ImportFrom', label='ImportFrom(val=root.package, [MyQuerySet])')
|
|
||||||
# graph.edge('MypyFile', 'ImportFrom')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# graph.node('ClassDef_MyQuerySet', label='ClassDef(name=MyQuerySet)')
|
|
||||||
# graph.edge('MypyFile', 'ClassDef_MyQuerySet')
|
|
||||||
#
|
|
||||||
# graph.node('FuncDef_mymethod', label='FuncDef(name=mymethod)')
|
|
||||||
# graph.edge('ClassDef_MyQuerySet', 'FuncDef_mymethod')
|
|
||||||
#
|
|
||||||
# graph.node('Args', label='Args')
|
|
||||||
# graph.edge('FuncDef_mymethod', 'Args')
|
|
||||||
#
|
|
||||||
# graph.node('Var_self', label='Var(name=self)')
|
|
||||||
# graph.edge('Args', 'Var_self')
|
|
||||||
#
|
|
||||||
# graph.node('Block', label='Block')
|
|
||||||
# graph.edge('FuncDef_mymethod', 'Block')
|
|
||||||
#
|
|
||||||
# graph.node('PassStmt')
|
|
||||||
# graph.edge('Block', 'PassStmt')
|
|
||||||
|
|
||||||
# graph.node('ExpressionStmt')
|
|
||||||
# graph.edge('MypyFile', 'ExpressionStmt')
|
|
||||||
#
|
|
||||||
# graph.node('CallExpr', label='CallExpr(val="MyQuerySet()")')
|
|
||||||
# graph.edge('ExpressionStmt', 'CallExpr')
|
|
||||||
#
|
|
||||||
# graph.node('MemberExpr', label='MemberExpr(val=".mymethod()")')
|
|
||||||
# graph.edge('CallExpr', 'MemberExpr')
|
|
||||||
#
|
|
||||||
# graph.node('CallExpr_outer_Args', label='Args()')
|
|
||||||
# graph.edge('CallExpr', 'CallExpr_outer_Args')
|
|
||||||
#
|
|
||||||
# graph.node('CallExpr_inner', label='CallExpr(val="mymethod()")')
|
|
||||||
# graph.edge('MemberExpr', 'CallExpr_inner')
|
|
||||||
#
|
|
||||||
# graph.node('NameExpr', label='NameExpr(val="mymethod")')
|
|
||||||
# graph.edge('CallExpr_inner', 'NameExpr')
|
|
||||||
#
|
|
||||||
# graph.node('Expression_Args', label='Args()')
|
|
||||||
# graph.edge('CallExpr_inner', 'Expression_Args')
|
|
||||||
|
|
||||||
graph.render(view=True, format='png')
|
|
||||||
|
|
||||||
|
|
||||||
# MypyFile(
|
|
||||||
# ClassDef(
|
|
||||||
# name=MyQuerySet,
|
|
||||||
# FuncDef(
|
|
||||||
# name=mymethod,
|
|
||||||
# Args(
|
|
||||||
# Var(self))
|
|
||||||
# Block(PassStmt())
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
# ExpressionStmt:6(
|
|
||||||
# CallExpr:6(
|
|
||||||
# MemberExpr:6(
|
|
||||||
# CallExpr:6(
|
|
||||||
# NameExpr(MyQuerySet)
|
|
||||||
# Args())
|
|
||||||
# mymethod)
|
|
||||||
# Args())))
|
|
||||||
13
mfile.py.gv
13
mfile.py.gv
@@ -1,13 +0,0 @@
|
|||||||
digraph "mfile.py" {
|
|
||||||
graph [fontcolor=blue labelloc=t margin=0]
|
|
||||||
node [fontcolor="#00008b"]
|
|
||||||
__builtins__
|
|
||||||
"django.db.models"
|
|
||||||
"django.db.models.fields"
|
|
||||||
"django.db.models" -> "django.db.models.fields"
|
|
||||||
"django.db.models" -> __builtins__
|
|
||||||
"django.db.models.fields" -> __builtins__
|
|
||||||
mymodule
|
|
||||||
mymodule -> "django.db.models"
|
|
||||||
mymodule -> __builtins__
|
|
||||||
}
|
|
||||||
BIN
mfile.py.gv.pdf
BIN
mfile.py.gv.pdf
Binary file not shown.
BIN
mfile.py.gv.png
BIN
mfile.py.gv.png
Binary file not shown.
|
Before Width: | Height: | Size: 30 KiB |
9
my.gv
9
my.gv
@@ -1,9 +0,0 @@
|
|||||||
digraph AST {
|
|
||||||
File
|
|
||||||
ClassDef
|
|
||||||
ClassDef -> File
|
|
||||||
FuncDef
|
|
||||||
FuncDef -> ClassDef
|
|
||||||
ExpressionStmt
|
|
||||||
ExpressionStmt -> File
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@ from django.db import models
|
|||||||
from django.db.models.base import Model
|
from django.db.models.base import Model
|
||||||
from django.db.models.fields import AutoField, CharField, Field
|
from django.db.models.fields import AutoField, CharField, Field
|
||||||
from django.db.models.fields.related import ForeignKey, RelatedField
|
from django.db.models.fields.related import ForeignKey, RelatedField
|
||||||
from django.db.models.fields.reverse_related import ForeignObjectRel, ManyToOneRel, ManyToManyRel, OneToOneRel
|
from django.db.models.fields.reverse_related import ForeignObjectRel
|
||||||
from django.db.models.lookups import Exact
|
from django.db.models.lookups import Exact
|
||||||
from django.db.models.sql.query import Query
|
from django.db.models.sql.query import Query
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
@@ -21,7 +21,7 @@ from mypy.types import AnyType, Instance
|
|||||||
from mypy.types import Type as MypyType
|
from mypy.types import Type as MypyType
|
||||||
from mypy.types import TypeOfAny, UnionType
|
from mypy.types import TypeOfAny, UnionType
|
||||||
|
|
||||||
from mypy_django_plugin.lib import chk_helpers, fullnames, helpers
|
from mypy_django_plugin.lib import fullnames, helpers
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
@@ -55,10 +55,10 @@ def initialize_django(settings_module: str) -> Tuple['Apps', 'LazySettings']:
|
|||||||
def noop_class_getitem(cls, key):
|
def noop_class_getitem(cls, key):
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
# from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
# models.QuerySet.__class_getitem__ = classmethod(noop_class_getitem) # type: ignore
|
models.QuerySet.__class_getitem__ = classmethod(noop_class_getitem) # type: ignore
|
||||||
# models.Manager.__class_getitem__ = classmethod(noop_class_getitem) # type: ignore
|
models.Manager.__class_getitem__ = classmethod(noop_class_getitem) # type: ignore
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
@@ -119,10 +119,10 @@ class DjangoContext:
|
|||||||
if isinstance(field, Field):
|
if isinstance(field, Field):
|
||||||
yield field
|
yield field
|
||||||
|
|
||||||
def get_model_relations(self, model_cls: Type[Model]) -> Iterator[Tuple[Optional[str], ForeignObjectRel]]:
|
def get_model_relations(self, model_cls: Type[Model]) -> Iterator[ForeignObjectRel]:
|
||||||
for relation in model_cls._meta.get_fields():
|
for field in model_cls._meta.get_fields():
|
||||||
if isinstance(relation, ForeignObjectRel):
|
if isinstance(field, ForeignObjectRel):
|
||||||
yield relation.get_accessor_name(), relation
|
yield field
|
||||||
|
|
||||||
def get_field_lookup_exact_type(self, api: TypeChecker, field: Union[Field, ForeignObjectRel]) -> MypyType:
|
def get_field_lookup_exact_type(self, api: TypeChecker, field: Union[Field, ForeignObjectRel]) -> MypyType:
|
||||||
if isinstance(field, (RelatedField, ForeignObjectRel)):
|
if isinstance(field, (RelatedField, ForeignObjectRel)):
|
||||||
@@ -226,7 +226,7 @@ class DjangoContext:
|
|||||||
attname = field.attname
|
attname = field.attname
|
||||||
return attname
|
return attname
|
||||||
|
|
||||||
def get_field_nullability(self, field: Union[Field, ForeignObjectRel], method: Optional[str] = None) -> bool:
|
def get_field_nullability(self, field: Union[Field, ForeignObjectRel], method: Optional[str]) -> bool:
|
||||||
nullable = field.null
|
nullable = field.null
|
||||||
if not nullable and isinstance(field, CharField) and field.blank:
|
if not nullable and isinstance(field, CharField) and field.blank:
|
||||||
return True
|
return True
|
||||||
@@ -356,11 +356,11 @@ class DjangoContext:
|
|||||||
return AnyType(TypeOfAny.explicit)
|
return AnyType(TypeOfAny.explicit)
|
||||||
|
|
||||||
if lookup_cls is None or isinstance(lookup_cls, Exact):
|
if lookup_cls is None or isinstance(lookup_cls, Exact):
|
||||||
return self.get_field_lookup_exact_type(chk_helpers.get_typechecker_api(ctx), field)
|
return self.get_field_lookup_exact_type(helpers.get_typechecker_api(ctx), field)
|
||||||
|
|
||||||
assert lookup_cls is not None
|
assert lookup_cls is not None
|
||||||
|
|
||||||
lookup_info = helpers.lookup_class_typeinfo(chk_helpers.get_typechecker_api(ctx), lookup_cls)
|
lookup_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), lookup_cls)
|
||||||
if lookup_info is None:
|
if lookup_info is None:
|
||||||
return AnyType(TypeOfAny.explicit)
|
return AnyType(TypeOfAny.explicit)
|
||||||
|
|
||||||
@@ -370,7 +370,7 @@ class DjangoContext:
|
|||||||
# if it's Field, consider lookup_type a __get__ of current field
|
# if it's Field, consider lookup_type a __get__ of current field
|
||||||
if (isinstance(lookup_type, Instance)
|
if (isinstance(lookup_type, Instance)
|
||||||
and lookup_type.type.fullname == fullnames.FIELD_FULLNAME):
|
and lookup_type.type.fullname == fullnames.FIELD_FULLNAME):
|
||||||
field_info = helpers.lookup_class_typeinfo(chk_helpers.get_typechecker_api(ctx), field.__class__)
|
field_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), field.__class__)
|
||||||
if field_info is None:
|
if field_info is None:
|
||||||
return AnyType(TypeOfAny.explicit)
|
return AnyType(TypeOfAny.explicit)
|
||||||
lookup_type = helpers.get_private_descriptor_type(field_info, '_pyi_private_get_type',
|
lookup_type = helpers.get_private_descriptor_type(field_info, '_pyi_private_get_type',
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
from typing import Dict, List, Optional, Set, Union
|
|
||||||
|
|
||||||
from mypy import checker
|
|
||||||
from mypy.checker import TypeChecker
|
|
||||||
from mypy.nodes import (
|
|
||||||
GDEF, MDEF, Expression, MypyFile, SymbolTableNode, TypeInfo, Var,
|
|
||||||
)
|
|
||||||
from mypy.plugin import (
|
|
||||||
AttributeContext, CheckerPluginInterface, FunctionContext, MethodContext,
|
|
||||||
)
|
|
||||||
from mypy.types import AnyType, Instance, TupleType
|
|
||||||
from mypy.types import Type as MypyType
|
|
||||||
from mypy.types import TypedDictType, TypeOfAny
|
|
||||||
|
|
||||||
from mypy_django_plugin.lib import helpers
|
|
||||||
|
|
||||||
|
|
||||||
def add_new_class_for_current_module(current_module: MypyFile,
|
|
||||||
name: str,
|
|
||||||
bases: List[Instance],
|
|
||||||
fields: Optional[Dict[str, MypyType]] = None
|
|
||||||
) -> TypeInfo:
|
|
||||||
new_class_unique_name = checker.gen_unique_name(name, current_module.names)
|
|
||||||
new_typeinfo = helpers.new_typeinfo(new_class_unique_name,
|
|
||||||
bases=bases,
|
|
||||||
module_name=current_module.fullname)
|
|
||||||
# new_typeinfo = helpers.make_new_typeinfo_in_current_module(new_class_unique_name,
|
|
||||||
# bases=bases,
|
|
||||||
# current_module_fullname=current_module.fullname)
|
|
||||||
# add fields
|
|
||||||
if fields:
|
|
||||||
for field_name, field_type in fields.items():
|
|
||||||
var = Var(field_name, type=field_type)
|
|
||||||
var.info = new_typeinfo
|
|
||||||
var._fullname = new_typeinfo.fullname + '.' + field_name
|
|
||||||
new_typeinfo.names[field_name] = SymbolTableNode(MDEF, var, plugin_generated=True)
|
|
||||||
|
|
||||||
current_module.names[new_class_unique_name] = SymbolTableNode(GDEF, new_typeinfo, plugin_generated=True)
|
|
||||||
current_module.defs.append(new_typeinfo.defn)
|
|
||||||
return new_typeinfo
|
|
||||||
|
|
||||||
|
|
||||||
def make_oneoff_named_tuple(api: TypeChecker, name: str, fields: 'Dict[str, MypyType]') -> TupleType:
|
|
||||||
current_module = helpers.get_current_module(api)
|
|
||||||
namedtuple_info = add_new_class_for_current_module(current_module, name,
|
|
||||||
bases=[api.named_generic_type('typing.NamedTuple', [])],
|
|
||||||
fields=fields)
|
|
||||||
return TupleType(list(fields.values()), fallback=Instance(namedtuple_info, []))
|
|
||||||
|
|
||||||
|
|
||||||
def make_tuple(api: 'TypeChecker', fields: List[MypyType]) -> TupleType:
|
|
||||||
# fallback for tuples is any builtins.tuple instance
|
|
||||||
fallback = api.named_generic_type('builtins.tuple',
|
|
||||||
[AnyType(TypeOfAny.special_form)])
|
|
||||||
return TupleType(fields, fallback=fallback)
|
|
||||||
|
|
||||||
|
|
||||||
def make_oneoff_typeddict(api: CheckerPluginInterface, fields: 'Dict[str, MypyType]',
|
|
||||||
required_keys: Set[str]) -> TypedDictType:
|
|
||||||
object_type = api.named_generic_type('mypy_extensions._TypedDict', [])
|
|
||||||
typed_dict_type = TypedDictType(fields, # type: ignore
|
|
||||||
required_keys=required_keys,
|
|
||||||
fallback=object_type)
|
|
||||||
return typed_dict_type
|
|
||||||
|
|
||||||
|
|
||||||
def get_typechecker_api(ctx: Union[AttributeContext, MethodContext, FunctionContext]) -> TypeChecker:
|
|
||||||
if not isinstance(ctx.api, TypeChecker):
|
|
||||||
raise ValueError('Not a TypeChecker')
|
|
||||||
return ctx.api
|
|
||||||
|
|
||||||
|
|
||||||
def check_types_compatible(ctx: Union[FunctionContext, MethodContext],
|
|
||||||
*, expected_type: MypyType, actual_type: MypyType, error_message: str) -> None:
|
|
||||||
api = get_typechecker_api(ctx)
|
|
||||||
api.check_subtype(actual_type, expected_type,
|
|
||||||
ctx.context, error_message,
|
|
||||||
'got', 'expected')
|
|
||||||
|
|
||||||
|
|
||||||
def get_call_argument_by_name(ctx: Union[FunctionContext, MethodContext], name: str) -> Optional[Expression]:
|
|
||||||
"""
|
|
||||||
Return the expression for the specific argument.
|
|
||||||
This helper should only be used with non-star arguments.
|
|
||||||
"""
|
|
||||||
if name not in ctx.callee_arg_names:
|
|
||||||
return None
|
|
||||||
idx = ctx.callee_arg_names.index(name)
|
|
||||||
args = ctx.args[idx]
|
|
||||||
if len(args) != 1:
|
|
||||||
# Either an error or no value passed.
|
|
||||||
return None
|
|
||||||
return args[0]
|
|
||||||
|
|
||||||
|
|
||||||
def get_call_argument_type_by_name(ctx: Union[FunctionContext, MethodContext], name: str) -> Optional[MypyType]:
|
|
||||||
"""Return the type for the specific argument.
|
|
||||||
|
|
||||||
This helper should only be used with non-star arguments.
|
|
||||||
"""
|
|
||||||
if name not in ctx.callee_arg_names:
|
|
||||||
return None
|
|
||||||
idx = ctx.callee_arg_names.index(name)
|
|
||||||
arg_types = ctx.arg_types[idx]
|
|
||||||
if len(arg_types) != 1:
|
|
||||||
# Either an error or no value passed.
|
|
||||||
return None
|
|
||||||
return arg_types[0]
|
|
||||||
|
|
||||||
|
|
||||||
def add_new_sym_for_info(info: TypeInfo, *, name: str, sym_type: MypyType) -> None:
|
|
||||||
# type=: type of the variable itself
|
|
||||||
var = Var(name=name, type=sym_type)
|
|
||||||
# var.info: type of the object variable is bound to
|
|
||||||
var.info = info
|
|
||||||
var._fullname = info.fullname + '.' + name
|
|
||||||
var.is_initialized_in_class = True
|
|
||||||
var.is_inferred = True
|
|
||||||
info.names[name] = SymbolTableNode(MDEF, var,
|
|
||||||
plugin_generated=True)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
def make_classes_generic(*klasses: type) -> None:
|
|
||||||
for klass in klasses:
|
|
||||||
def fake_classgetitem(cls, *args, **kwargs):
|
|
||||||
return cls
|
|
||||||
|
|
||||||
klass.__class_getitem__ = classmethod(fake_classgetitem) # type: ignore
|
|
||||||
@@ -1,372 +1,70 @@
|
|||||||
from abc import abstractmethod
|
from collections import OrderedDict
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union,
|
TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Optional, Set, Tuple, Union,
|
||||||
cast)
|
)
|
||||||
|
|
||||||
|
from django.db.models.fields import Field
|
||||||
from django.db.models.fields.related import RelatedField
|
from django.db.models.fields.related import RelatedField
|
||||||
from django.db.models.fields.reverse_related import ForeignObjectRel
|
from django.db.models.fields.reverse_related import ForeignObjectRel
|
||||||
|
from mypy import checker
|
||||||
from mypy.checker import TypeChecker
|
from mypy.checker import TypeChecker
|
||||||
from mypy.mro import calculate_mro
|
from mypy.mro import calculate_mro
|
||||||
from mypy.nodes import (
|
from mypy.nodes import (
|
||||||
Block, ClassDef, Expression, MemberExpr, MypyFile, NameExpr, StrExpr, SymbolTable, SymbolTableNode,
|
GDEF, MDEF, Argument, Block, ClassDef, Expression, FuncDef, MemberExpr, MypyFile, NameExpr, PlaceholderNode,
|
||||||
TypeInfo, Var,
|
StrExpr, SymbolNode, SymbolTable, SymbolTableNode, TypeInfo, Var,
|
||||||
CallExpr, Context, PlaceholderNode, FuncDef, FakeInfo, OverloadedFuncDef, Decorator)
|
)
|
||||||
from mypy.plugin import DynamicClassDefContext, ClassDefContext, AttributeContext, MethodContext
|
from mypy.plugin import (
|
||||||
|
AttributeContext, CheckerPluginInterface, ClassDefContext, DynamicClassDefContext, FunctionContext, MethodContext,
|
||||||
|
)
|
||||||
from mypy.plugins.common import add_method
|
from mypy.plugins.common import add_method
|
||||||
from mypy.semanal import SemanticAnalyzer, is_valid_replacement, is_same_symbol
|
from mypy.semanal import SemanticAnalyzer
|
||||||
from mypy.types import AnyType, Instance, NoneTyp, TypeType, ProperType, CallableType
|
from mypy.types import AnyType, CallableType, Instance, NoneTyp, TupleType
|
||||||
from mypy.types import Type as MypyType
|
from mypy.types import Type as MypyType
|
||||||
from mypy.types import TypeOfAny, UnionType
|
from mypy.types import TypedDictType, TypeOfAny, UnionType
|
||||||
from mypy.typetraverser import TypeTraverserVisitor
|
|
||||||
|
|
||||||
from django.db.models.fields import Field
|
|
||||||
from mypy_django_plugin.lib import fullnames
|
from mypy_django_plugin.lib import fullnames
|
||||||
from mypy_django_plugin.lib.sem_helpers import prepare_unannotated_method_signature, analyze_callable_signature
|
|
||||||
from mypy_django_plugin.transformers2 import new_helpers
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from mypy_django_plugin.django.context import DjangoContext
|
from mypy_django_plugin.django.context import DjangoContext
|
||||||
from mypy_django_plugin.main import NewSemanalDjangoPlugin
|
|
||||||
|
|
||||||
AnyPluginAPI = Union[TypeChecker, SemanticAnalyzer]
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoPluginCallback:
|
|
||||||
django_context: 'DjangoContext'
|
|
||||||
|
|
||||||
def __init__(self, plugin: 'NewSemanalDjangoPlugin') -> None:
|
|
||||||
self.plugin = plugin
|
|
||||||
self.django_context = plugin.django_context
|
|
||||||
|
|
||||||
def new_typeinfo(self, name: str, bases: List[Instance]) -> TypeInfo:
|
|
||||||
class_def = ClassDef(name, Block([]))
|
|
||||||
class_def.fullname = self.qualified_name(name)
|
|
||||||
|
|
||||||
info = TypeInfo(SymbolTable(), class_def, self.get_current_module().fullname)
|
|
||||||
info.bases = bases
|
|
||||||
calculate_mro(info)
|
|
||||||
info.metaclass_type = info.calculate_metaclass_type()
|
|
||||||
|
|
||||||
class_def.info = info
|
|
||||||
return info
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_current_module(self) -> MypyFile:
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def qualified_name(self, name: str) -> str:
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
|
|
||||||
class SemanalPluginCallback(DjangoPluginCallback):
|
|
||||||
semanal_api: SemanticAnalyzer
|
|
||||||
|
|
||||||
def build_defer_error_message(self, message: str) -> str:
|
|
||||||
return f'{self.__class__.__name__}: {message}'
|
|
||||||
|
|
||||||
def defer_till_next_iteration(self, deferral_context: Optional[Context] = None,
|
|
||||||
*,
|
|
||||||
reason: Optional[str] = None) -> bool:
|
|
||||||
""" Returns False if cannot be deferred. """
|
|
||||||
if self.semanal_api.final_iteration:
|
|
||||||
return False
|
|
||||||
self.semanal_api.defer(deferral_context)
|
|
||||||
print(f'LOG: defer: {self.build_defer_error_message(reason)}')
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_current_module(self) -> MypyFile:
|
|
||||||
return self.semanal_api.cur_mod_node
|
|
||||||
|
|
||||||
def qualified_name(self, name: str) -> str:
|
|
||||||
return self.semanal_api.qualified_name(name)
|
|
||||||
|
|
||||||
def lookup_typeinfo_or_defer(self, fullname: str, *,
|
|
||||||
deferral_context: Optional[Context] = None,
|
|
||||||
reason_for_defer: Optional[str] = None) -> Optional[TypeInfo]:
|
|
||||||
sym = self.plugin.lookup_fully_qualified(fullname)
|
|
||||||
if sym is None or sym.node is None or isinstance(sym.node, PlaceholderNode):
|
|
||||||
deferral_context = deferral_context or self.semanal_api.cur_mod_node
|
|
||||||
reason = reason_for_defer or f'{fullname!r} is not available for lookup'
|
|
||||||
if not self.defer_till_next_iteration(deferral_context, reason=reason):
|
|
||||||
raise new_helpers.TypeInfoNotFound(fullname)
|
|
||||||
return None
|
|
||||||
|
|
||||||
if not isinstance(sym.node, TypeInfo):
|
|
||||||
raise ValueError(f'{fullname!r} does not correspond to TypeInfo')
|
|
||||||
|
|
||||||
return sym.node
|
|
||||||
|
|
||||||
def new_typeinfo(self, name: str, bases: List[Instance], module_fullname: Optional[str] = None) -> TypeInfo:
|
|
||||||
class_def = ClassDef(name, Block([]))
|
|
||||||
class_def.fullname = self.semanal_api.qualified_name(name)
|
|
||||||
|
|
||||||
info = TypeInfo(SymbolTable(), class_def,
|
|
||||||
module_fullname or self.get_current_module().fullname)
|
|
||||||
info.bases = bases
|
|
||||||
calculate_mro(info)
|
|
||||||
info.metaclass_type = info.calculate_metaclass_type()
|
|
||||||
|
|
||||||
class_def.info = info
|
|
||||||
return info
|
|
||||||
|
|
||||||
def add_symbol_table_node(self,
|
|
||||||
name: str,
|
|
||||||
symbol: SymbolTableNode,
|
|
||||||
symbol_table: Optional[SymbolTable] = None,
|
|
||||||
context: Optional[Context] = None,
|
|
||||||
can_defer: bool = True,
|
|
||||||
escape_comprehensions: bool = False) -> None:
|
|
||||||
""" Patched copy of SemanticAnalyzer.add_symbol_table_node(). """
|
|
||||||
names = symbol_table or self.semanal_api.current_symbol_table(escape_comprehensions=escape_comprehensions)
|
|
||||||
existing = names.get(name)
|
|
||||||
if isinstance(symbol.node, PlaceholderNode) and can_defer:
|
|
||||||
self.semanal_api.defer(context)
|
|
||||||
return None
|
|
||||||
if (existing is not None
|
|
||||||
and context is not None
|
|
||||||
and not is_valid_replacement(existing, symbol)):
|
|
||||||
# There is an existing node, so this may be a redefinition.
|
|
||||||
# If the new node points to the same node as the old one,
|
|
||||||
# or if both old and new nodes are placeholders, we don't
|
|
||||||
# need to do anything.
|
|
||||||
old = existing.node
|
|
||||||
new = symbol.node
|
|
||||||
if isinstance(new, PlaceholderNode):
|
|
||||||
# We don't know whether this is okay. Let's wait until the next iteration.
|
|
||||||
return False
|
|
||||||
if not is_same_symbol(old, new):
|
|
||||||
if isinstance(new, (FuncDef, Decorator, OverloadedFuncDef, TypeInfo)):
|
|
||||||
self.semanal_api.add_redefinition(names, name, symbol)
|
|
||||||
if not (isinstance(new, (FuncDef, Decorator))
|
|
||||||
and self.semanal_api.set_original_def(old, new)):
|
|
||||||
self.semanal_api.name_already_defined(name, context, existing)
|
|
||||||
elif name not in self.semanal_api.missing_names and '*' not in self.semanal_api.missing_names:
|
|
||||||
names[name] = symbol
|
|
||||||
self.progress = True
|
|
||||||
return None
|
|
||||||
raise new_helpers.SymbolAdditionNotPossible()
|
|
||||||
|
|
||||||
# def add_symbol_table_node_or_defer(self, name: str, sym: SymbolTableNode) -> bool:
|
|
||||||
# return self.semanal_api.add_symbol_table_node(name, sym,
|
|
||||||
# context=self.semanal_api.cur_mod_node)
|
|
||||||
|
|
||||||
def add_method_from_signature(self,
|
|
||||||
signature_node: FuncDef,
|
|
||||||
new_method_name: str,
|
|
||||||
new_self_type: Instance,
|
|
||||||
class_defn: ClassDef) -> bool:
|
|
||||||
if signature_node.type is None:
|
|
||||||
if self.defer_till_next_iteration(reason=signature_node.fullname):
|
|
||||||
return False
|
|
||||||
|
|
||||||
arguments, return_type = prepare_unannotated_method_signature(signature_node)
|
|
||||||
ctx = ClassDefContext(class_defn, signature_node, self.semanal_api)
|
|
||||||
add_method(ctx,
|
|
||||||
new_method_name,
|
|
||||||
self_type=new_self_type,
|
|
||||||
args=arguments,
|
|
||||||
return_type=return_type)
|
|
||||||
return True
|
|
||||||
|
|
||||||
# add imported objects from method signature to the current module, if not present
|
|
||||||
source_symbols = self.semanal_api.modules[signature_node.info.module_name].names
|
|
||||||
currently_imported_symbols = self.semanal_api.cur_mod_node.names
|
|
||||||
|
|
||||||
def import_symbol_from_source(name: str) -> None:
|
|
||||||
if name in source_symbols['__builtins__'].node.names:
|
|
||||||
return
|
|
||||||
sym = source_symbols[name].copy()
|
|
||||||
self.semanal_api.add_imported_symbol(name, sym, context=self.semanal_api.cur_mod_node)
|
|
||||||
|
|
||||||
class UnimportedTypesVisitor(TypeTraverserVisitor):
|
|
||||||
def visit_instance(self, t: Instance) -> None:
|
|
||||||
super().visit_instance(t)
|
|
||||||
if isinstance(t.type, FakeInfo):
|
|
||||||
return
|
|
||||||
type_name = t.type.name
|
|
||||||
sym = currently_imported_symbols.get(type_name)
|
|
||||||
if sym is None:
|
|
||||||
import_symbol_from_source(type_name)
|
|
||||||
|
|
||||||
signature_node.type.accept(UnimportedTypesVisitor())
|
|
||||||
|
|
||||||
# # copy global SymbolTableNode objects from original class to the current node, if not present
|
|
||||||
# original_module = semanal_api.modules[method_node.info.module_name]
|
|
||||||
# for name, sym in original_module.names.items():
|
|
||||||
# if (not sym.plugin_generated
|
|
||||||
# and name not in semanal_api.cur_mod_node.names):
|
|
||||||
# semanal_api.add_imported_symbol(name, sym, context=semanal_api.cur_mod_node)
|
|
||||||
|
|
||||||
arguments, analyzed_return_type, unbound = analyze_callable_signature(self.semanal_api, signature_node)
|
|
||||||
if unbound:
|
|
||||||
raise new_helpers.IncompleteDefnError(f'Signature of method {signature_node.fullname!r} is not ready')
|
|
||||||
|
|
||||||
assert len(arguments) + 1 == len(signature_node.arguments)
|
|
||||||
assert analyzed_return_type is not None
|
|
||||||
|
|
||||||
ctx = ClassDefContext(class_defn, signature_node, self.semanal_api)
|
|
||||||
add_method(ctx,
|
|
||||||
new_method_name,
|
|
||||||
self_type=new_self_type,
|
|
||||||
args=arguments,
|
|
||||||
return_type=analyzed_return_type)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class DynamicClassPluginCallback(SemanalPluginCallback):
|
|
||||||
class_name: str
|
|
||||||
call_expr: CallExpr
|
|
||||||
|
|
||||||
def __call__(self, ctx: DynamicClassDefContext) -> None:
|
|
||||||
self.class_name = ctx.name
|
|
||||||
self.call_expr = ctx.call
|
|
||||||
self.semanal_api = cast(SemanticAnalyzer, ctx.api)
|
|
||||||
self.create_new_dynamic_class()
|
|
||||||
|
|
||||||
def get_callee(self) -> MemberExpr:
|
|
||||||
callee = self.call_expr.callee
|
|
||||||
assert isinstance(callee, MemberExpr)
|
|
||||||
return callee
|
|
||||||
|
|
||||||
def lookup_same_module_or_defer(self, name: str, *,
|
|
||||||
deferral_context: Optional[Context] = None) -> Optional[SymbolTableNode]:
|
|
||||||
sym = self.semanal_api.lookup_qualified(name, self.call_expr)
|
|
||||||
if sym is None or sym.node is None or isinstance(sym.node, PlaceholderNode):
|
|
||||||
deferral_context = deferral_context or self.call_expr
|
|
||||||
if not self.defer_till_next_iteration(deferral_context,
|
|
||||||
reason=f'{self.semanal_api.cur_mod_id}.{name} does not exist'):
|
|
||||||
raise new_helpers.NameNotFound(name)
|
|
||||||
return None
|
|
||||||
return sym
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def create_new_dynamic_class(self) -> None:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class ClassDefPluginCallback(SemanalPluginCallback):
|
|
||||||
reason: Expression
|
|
||||||
class_defn: ClassDef
|
|
||||||
ctx: ClassDefContext
|
|
||||||
|
|
||||||
def __call__(self, ctx: ClassDefContext) -> None:
|
|
||||||
self.reason = ctx.reason
|
|
||||||
self.class_defn = ctx.cls
|
|
||||||
self.semanal_api = cast(SemanticAnalyzer, ctx.api)
|
|
||||||
self.ctx = ctx
|
|
||||||
self.modify_class_defn()
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def modify_class_defn(self) -> None:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class TypeCheckerPluginCallback(DjangoPluginCallback):
|
|
||||||
type_checker: TypeChecker
|
|
||||||
|
|
||||||
def get_current_module(self) -> MypyFile:
|
|
||||||
current_module = None
|
|
||||||
for item in reversed(self.type_checker.scope.stack):
|
|
||||||
if isinstance(item, MypyFile):
|
|
||||||
current_module = item
|
|
||||||
break
|
|
||||||
assert current_module is not None
|
|
||||||
return current_module
|
|
||||||
|
|
||||||
def qualified_name(self, name: str) -> str:
|
|
||||||
return self.type_checker.scope.stack[-1].fullname + '.' + name
|
|
||||||
|
|
||||||
def lookup_typeinfo(self, fullname: str) -> Optional[TypeInfo]:
|
|
||||||
sym = self.plugin.lookup_fully_qualified(fullname)
|
|
||||||
if sym is None or sym.node is None:
|
|
||||||
return None
|
|
||||||
if not isinstance(sym.node, TypeInfo):
|
|
||||||
raise ValueError(f'{fullname!r} does not correspond to TypeInfo')
|
|
||||||
return sym.node
|
|
||||||
|
|
||||||
|
|
||||||
class GetMethodPluginCallback(TypeCheckerPluginCallback):
|
|
||||||
callee_type: Instance
|
|
||||||
ctx: MethodContext
|
|
||||||
|
|
||||||
def __call__(self, ctx: MethodContext) -> MypyType:
|
|
||||||
self.type_checker = ctx.api
|
|
||||||
|
|
||||||
assert isinstance(ctx.type, CallableType)
|
|
||||||
self.callee_type = ctx.type.ret_type
|
|
||||||
self.ctx = ctx
|
|
||||||
return self.get_method_return_type()
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_method_return_type(self) -> MypyType:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class GetAttributeCallback(TypeCheckerPluginCallback):
|
|
||||||
obj_type: ProperType
|
|
||||||
default_attr_type: MypyType
|
|
||||||
error_context: MemberExpr
|
|
||||||
name: str
|
|
||||||
|
|
||||||
def __call__(self, ctx: AttributeContext) -> MypyType:
|
|
||||||
self.ctx = ctx
|
|
||||||
self.type_checker = ctx.api
|
|
||||||
self.obj_type = ctx.type
|
|
||||||
self.default_attr_type = ctx.default_attr_type
|
|
||||||
self.error_context = ctx.context
|
|
||||||
assert isinstance(self.error_context, MemberExpr)
|
|
||||||
self.name = self.error_context.name
|
|
||||||
return self.default_attr_type
|
|
||||||
|
|
||||||
|
|
||||||
def get_django_metadata(model_info: TypeInfo) -> Dict[str, Any]:
|
def get_django_metadata(model_info: TypeInfo) -> Dict[str, Any]:
|
||||||
return model_info.metadata.setdefault('django', {})
|
return model_info.metadata.setdefault('django', {})
|
||||||
|
|
||||||
|
|
||||||
def split_symbol_name(fullname: str, all_modules: Dict[str, MypyFile]) -> Optional[Tuple[str, str]]:
|
class IncompleteDefnException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def lookup_fully_qualified_sym(fullname: str, all_modules: Dict[str, MypyFile]) -> Optional[SymbolTableNode]:
|
||||||
if '.' not in fullname:
|
if '.' not in fullname:
|
||||||
return None
|
return None
|
||||||
module_name = None
|
module, cls_name = fullname.rsplit('.', 1)
|
||||||
parts = fullname.split('.')
|
|
||||||
for i in range(len(parts), 0, -1):
|
module_file = all_modules.get(module)
|
||||||
possible_module_name = '.'.join(parts[:i])
|
if module_file is None:
|
||||||
if possible_module_name in all_modules:
|
|
||||||
module_name = possible_module_name
|
|
||||||
break
|
|
||||||
if module_name is None:
|
|
||||||
return None
|
return None
|
||||||
|
sym = module_file.names.get(cls_name)
|
||||||
symbol_name = fullname.replace(module_name, '').lstrip('.')
|
if sym is None:
|
||||||
return module_name, symbol_name
|
|
||||||
|
|
||||||
|
|
||||||
def lookup_fully_qualified_typeinfo(api: AnyPluginAPI, fullname: str) -> Optional[TypeInfo]:
|
|
||||||
split = split_symbol_name(fullname, api.modules)
|
|
||||||
if split is None:
|
|
||||||
return None
|
return None
|
||||||
module_name, cls_name = split
|
return sym
|
||||||
|
|
||||||
sym_table = api.modules[module_name].names # type: Dict[str, SymbolTableNode]
|
|
||||||
if '.' in cls_name:
|
|
||||||
parent_cls_name, _, cls_name = cls_name.rpartition('.')
|
|
||||||
# nested class
|
|
||||||
for parent_cls_name in parent_cls_name.split('.'):
|
|
||||||
sym = sym_table.get(parent_cls_name)
|
|
||||||
if (sym is None or sym.node is None
|
|
||||||
or not isinstance(sym.node, TypeInfo)):
|
|
||||||
return None
|
|
||||||
sym_table = sym.node.names
|
|
||||||
|
|
||||||
sym = sym_table.get(cls_name)
|
def lookup_fully_qualified_generic(name: str, all_modules: Dict[str, MypyFile]) -> Optional[SymbolNode]:
|
||||||
if (sym is None
|
sym = lookup_fully_qualified_sym(name, all_modules)
|
||||||
or sym.node is None
|
if sym is None:
|
||||||
or not isinstance(sym.node, TypeInfo)):
|
|
||||||
return None
|
return None
|
||||||
return sym.node
|
return sym.node
|
||||||
|
|
||||||
|
|
||||||
def lookup_class_typeinfo(api: AnyPluginAPI, klass: type) -> Optional[TypeInfo]:
|
def lookup_fully_qualified_typeinfo(api: Union[TypeChecker, SemanticAnalyzer], fullname: str) -> Optional[TypeInfo]:
|
||||||
|
node = lookup_fully_qualified_generic(fullname, api.modules)
|
||||||
|
if not isinstance(node, TypeInfo):
|
||||||
|
return None
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
def lookup_class_typeinfo(api: TypeChecker, klass: type) -> Optional[TypeInfo]:
|
||||||
fullname = get_class_fullname(klass)
|
fullname = get_class_fullname(klass)
|
||||||
field_info = lookup_fully_qualified_typeinfo(api, fullname)
|
field_info = lookup_fully_qualified_typeinfo(api, fullname)
|
||||||
return field_info
|
return field_info
|
||||||
@@ -381,6 +79,36 @@ def get_class_fullname(klass: type) -> str:
|
|||||||
return klass.__module__ + '.' + klass.__qualname__
|
return klass.__module__ + '.' + klass.__qualname__
|
||||||
|
|
||||||
|
|
||||||
|
def get_call_argument_by_name(ctx: Union[FunctionContext, MethodContext], name: str) -> Optional[Expression]:
|
||||||
|
"""
|
||||||
|
Return the expression for the specific argument.
|
||||||
|
This helper should only be used with non-star arguments.
|
||||||
|
"""
|
||||||
|
if name not in ctx.callee_arg_names:
|
||||||
|
return None
|
||||||
|
idx = ctx.callee_arg_names.index(name)
|
||||||
|
args = ctx.args[idx]
|
||||||
|
if len(args) != 1:
|
||||||
|
# Either an error or no value passed.
|
||||||
|
return None
|
||||||
|
return args[0]
|
||||||
|
|
||||||
|
|
||||||
|
def get_call_argument_type_by_name(ctx: Union[FunctionContext, MethodContext], name: str) -> Optional[MypyType]:
|
||||||
|
"""Return the type for the specific argument.
|
||||||
|
|
||||||
|
This helper should only be used with non-star arguments.
|
||||||
|
"""
|
||||||
|
if name not in ctx.callee_arg_names:
|
||||||
|
return None
|
||||||
|
idx = ctx.callee_arg_names.index(name)
|
||||||
|
arg_types = ctx.arg_types[idx]
|
||||||
|
if len(arg_types) != 1:
|
||||||
|
# Either an error or no value passed.
|
||||||
|
return None
|
||||||
|
return arg_types[0]
|
||||||
|
|
||||||
|
|
||||||
def make_optional(typ: MypyType) -> MypyType:
|
def make_optional(typ: MypyType) -> MypyType:
|
||||||
return UnionType.make_union([typ, NoneTyp()])
|
return UnionType.make_union([typ, NoneTyp()])
|
||||||
|
|
||||||
@@ -425,7 +153,7 @@ def get_private_descriptor_type(type_info: TypeInfo, private_field_name: str, is
|
|||||||
return AnyType(TypeOfAny.explicit)
|
return AnyType(TypeOfAny.explicit)
|
||||||
|
|
||||||
|
|
||||||
def get_field_lookup_exact_type(api: AnyPluginAPI, field: Field) -> MypyType:
|
def get_field_lookup_exact_type(api: TypeChecker, field: Field) -> MypyType:
|
||||||
if isinstance(field, (RelatedField, ForeignObjectRel)):
|
if isinstance(field, (RelatedField, ForeignObjectRel)):
|
||||||
lookup_type_class = field.related_model
|
lookup_type_class = field.related_model
|
||||||
rel_model_info = lookup_class_typeinfo(api, lookup_type_class)
|
rel_model_info = lookup_class_typeinfo(api, lookup_type_class)
|
||||||
@@ -440,10 +168,44 @@ def get_field_lookup_exact_type(api: AnyPluginAPI, field: Field) -> MypyType:
|
|||||||
is_nullable=field.null)
|
is_nullable=field.null)
|
||||||
|
|
||||||
|
|
||||||
def get_current_module(api: AnyPluginAPI) -> MypyFile:
|
def get_nested_meta_node_for_current_class(info: TypeInfo) -> Optional[TypeInfo]:
|
||||||
if isinstance(api, SemanticAnalyzer):
|
metaclass_sym = info.names.get('Meta')
|
||||||
return api.cur_mod_node
|
if metaclass_sym is not None and isinstance(metaclass_sym.node, TypeInfo):
|
||||||
|
return metaclass_sym.node
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def add_new_class_for_module(module: MypyFile,
|
||||||
|
name: str,
|
||||||
|
bases: List[Instance],
|
||||||
|
fields: Optional[Dict[str, MypyType]] = None
|
||||||
|
) -> TypeInfo:
|
||||||
|
new_class_unique_name = checker.gen_unique_name(name, module.names)
|
||||||
|
|
||||||
|
# make new class expression
|
||||||
|
classdef = ClassDef(new_class_unique_name, Block([]))
|
||||||
|
classdef.fullname = module.fullname + '.' + new_class_unique_name
|
||||||
|
|
||||||
|
# make new TypeInfo
|
||||||
|
new_typeinfo = TypeInfo(SymbolTable(), classdef, module.fullname)
|
||||||
|
new_typeinfo.bases = bases
|
||||||
|
calculate_mro(new_typeinfo)
|
||||||
|
new_typeinfo.calculate_metaclass_type()
|
||||||
|
|
||||||
|
# add fields
|
||||||
|
if fields:
|
||||||
|
for field_name, field_type in fields.items():
|
||||||
|
var = Var(field_name, type=field_type)
|
||||||
|
var.info = new_typeinfo
|
||||||
|
var._fullname = new_typeinfo.fullname + '.' + field_name
|
||||||
|
new_typeinfo.names[field_name] = SymbolTableNode(MDEF, var, plugin_generated=True)
|
||||||
|
|
||||||
|
classdef.info = new_typeinfo
|
||||||
|
module.names[new_class_unique_name] = SymbolTableNode(GDEF, new_typeinfo, plugin_generated=True)
|
||||||
|
return new_typeinfo
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_module(api: TypeChecker) -> MypyFile:
|
||||||
current_module = None
|
current_module = None
|
||||||
for item in reversed(api.scope.stack):
|
for item in reversed(api.scope.stack):
|
||||||
if isinstance(item, MypyFile):
|
if isinstance(item, MypyFile):
|
||||||
@@ -453,6 +215,21 @@ def get_current_module(api: AnyPluginAPI) -> MypyFile:
|
|||||||
return current_module
|
return current_module
|
||||||
|
|
||||||
|
|
||||||
|
def make_oneoff_named_tuple(api: TypeChecker, name: str, fields: 'OrderedDict[str, MypyType]') -> TupleType:
|
||||||
|
current_module = get_current_module(api)
|
||||||
|
namedtuple_info = add_new_class_for_module(current_module, name,
|
||||||
|
bases=[api.named_generic_type('typing.NamedTuple', [])],
|
||||||
|
fields=fields)
|
||||||
|
return TupleType(list(fields.values()), fallback=Instance(namedtuple_info, []))
|
||||||
|
|
||||||
|
|
||||||
|
def make_tuple(api: 'TypeChecker', fields: List[MypyType]) -> TupleType:
|
||||||
|
# fallback for tuples is any builtins.tuple instance
|
||||||
|
fallback = api.named_generic_type('builtins.tuple',
|
||||||
|
[AnyType(TypeOfAny.special_form)])
|
||||||
|
return TupleType(fields, fallback=fallback)
|
||||||
|
|
||||||
|
|
||||||
def convert_any_to_type(typ: MypyType, referred_to_type: MypyType) -> MypyType:
|
def convert_any_to_type(typ: MypyType, referred_to_type: MypyType) -> MypyType:
|
||||||
if isinstance(typ, UnionType):
|
if isinstance(typ, UnionType):
|
||||||
converted_items = []
|
converted_items = []
|
||||||
@@ -475,6 +252,13 @@ def convert_any_to_type(typ: MypyType, referred_to_type: MypyType) -> MypyType:
|
|||||||
return typ
|
return typ
|
||||||
|
|
||||||
|
|
||||||
|
def make_typeddict(api: CheckerPluginInterface, fields: 'OrderedDict[str, MypyType]',
|
||||||
|
required_keys: Set[str]) -> TypedDictType:
|
||||||
|
object_type = api.named_generic_type('mypy_extensions._TypedDict', [])
|
||||||
|
typed_dict_type = TypedDictType(fields, required_keys=required_keys, fallback=object_type)
|
||||||
|
return typed_dict_type
|
||||||
|
|
||||||
|
|
||||||
def resolve_string_attribute_value(attr_expr: Expression, django_context: 'DjangoContext') -> Optional[str]:
|
def resolve_string_attribute_value(attr_expr: Expression, django_context: 'DjangoContext') -> Optional[str]:
|
||||||
if isinstance(attr_expr, StrExpr):
|
if isinstance(attr_expr, StrExpr):
|
||||||
return attr_expr.value
|
return attr_expr.value
|
||||||
@@ -488,25 +272,104 @@ def resolve_string_attribute_value(attr_expr: Expression, django_context: 'Djang
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def is_subclass_of_model(info: TypeInfo, django_context: 'DjangoContext') -> bool:
|
def get_semanal_api(ctx: Union[ClassDefContext, DynamicClassDefContext]) -> SemanticAnalyzer:
|
||||||
|
if not isinstance(ctx.api, SemanticAnalyzer):
|
||||||
|
raise ValueError('Not a SemanticAnalyzer')
|
||||||
|
return ctx.api
|
||||||
|
|
||||||
|
|
||||||
|
def get_typechecker_api(ctx: Union[AttributeContext, MethodContext, FunctionContext]) -> TypeChecker:
|
||||||
|
if not isinstance(ctx.api, TypeChecker):
|
||||||
|
raise ValueError('Not a TypeChecker')
|
||||||
|
return ctx.api
|
||||||
|
|
||||||
|
|
||||||
|
def is_model_subclass_info(info: TypeInfo, django_context: 'DjangoContext') -> bool:
|
||||||
return (info.fullname in django_context.all_registered_model_class_fullnames
|
return (info.fullname in django_context.all_registered_model_class_fullnames
|
||||||
or info.has_base(fullnames.MODEL_CLASS_FULLNAME))
|
or info.has_base(fullnames.MODEL_CLASS_FULLNAME))
|
||||||
|
|
||||||
|
|
||||||
def new_typeinfo(name: str,
|
def check_types_compatible(ctx: Union[FunctionContext, MethodContext],
|
||||||
*,
|
*, expected_type: MypyType, actual_type: MypyType, error_message: str) -> None:
|
||||||
bases: List[Instance],
|
api = get_typechecker_api(ctx)
|
||||||
module_name: str) -> TypeInfo:
|
api.check_subtype(actual_type, expected_type,
|
||||||
"""
|
ctx.context, error_message,
|
||||||
Construct new TypeInfo instance. Cannot be used for nested classes.
|
'got', 'expected')
|
||||||
"""
|
|
||||||
class_def = ClassDef(name, Block([]))
|
|
||||||
class_def.fullname = module_name + '.' + name
|
|
||||||
|
|
||||||
info = TypeInfo(SymbolTable(), class_def, module_name)
|
|
||||||
info.bases = bases
|
|
||||||
calculate_mro(info)
|
|
||||||
info.metaclass_type = info.calculate_metaclass_type()
|
|
||||||
|
|
||||||
class_def.info = info
|
def add_new_sym_for_info(info: TypeInfo, *, name: str, sym_type: MypyType) -> None:
|
||||||
return info
|
# type=: type of the variable itself
|
||||||
|
var = Var(name=name, type=sym_type)
|
||||||
|
# var.info: type of the object variable is bound to
|
||||||
|
var.info = info
|
||||||
|
var._fullname = info.fullname + '.' + name
|
||||||
|
var.is_initialized_in_class = True
|
||||||
|
var.is_inferred = True
|
||||||
|
info.names[name] = SymbolTableNode(MDEF, var,
|
||||||
|
plugin_generated=True)
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
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=bound_return_type,
|
||||||
|
self_type=self_type)
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
from typing import List, NamedTuple, Optional, Tuple, Union, cast
|
|
||||||
|
|
||||||
from mypy.nodes import Argument, FuncDef, TypeInfo, Var
|
|
||||||
from mypy.plugin import ClassDefContext, DynamicClassDefContext
|
|
||||||
from mypy.plugins.common import add_method
|
|
||||||
from mypy.semanal import SemanticAnalyzer
|
|
||||||
from mypy.types import AnyType, CallableType, Instance, PlaceholderType
|
|
||||||
from mypy.types import Type as MypyType
|
|
||||||
from mypy.types import TypeOfAny, get_proper_type
|
|
||||||
|
|
||||||
|
|
||||||
class IncompleteDefnError(Exception):
|
|
||||||
def __init__(self, error_message: str = '') -> None:
|
|
||||||
super().__init__(error_message)
|
|
||||||
|
|
||||||
|
|
||||||
class BoundNameNotFound(IncompleteDefnError):
|
|
||||||
def __init__(self, fullname: str) -> None:
|
|
||||||
super().__init__(f'No {fullname!r} found')
|
|
||||||
|
|
||||||
|
|
||||||
def get_semanal_api(ctx: Union[ClassDefContext, DynamicClassDefContext]) -> SemanticAnalyzer:
|
|
||||||
return cast(SemanticAnalyzer, ctx.api)
|
|
||||||
|
|
||||||
|
|
||||||
def get_nested_meta_node_for_current_class(info: TypeInfo) -> Optional[TypeInfo]:
|
|
||||||
metaclass_sym = info.names.get('Meta')
|
|
||||||
if metaclass_sym is not None and isinstance(metaclass_sym.node, TypeInfo):
|
|
||||||
return metaclass_sym.node
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_unannotated_method_signature(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
|
|
||||||
|
|
||||||
|
|
||||||
class SignatureTuple(NamedTuple):
|
|
||||||
arguments: List[Argument]
|
|
||||||
return_type: Optional[MypyType]
|
|
||||||
cannot_be_bound: bool
|
|
||||||
|
|
||||||
|
|
||||||
def analyze_callable_signature(api: SemanticAnalyzer, method_node: FuncDef) -> SignatureTuple:
|
|
||||||
method_type = method_node.type
|
|
||||||
assert isinstance(method_type, CallableType)
|
|
||||||
|
|
||||||
arguments = []
|
|
||||||
unbound = False
|
|
||||||
for arg_name, arg_type, original_argument in zip(method_type.arg_names[1:],
|
|
||||||
method_type.arg_types[1:],
|
|
||||||
method_node.arguments[1:]):
|
|
||||||
analyzed_arg_type = api.anal_type(get_proper_type(arg_type), allow_placeholder=True)
|
|
||||||
assert analyzed_arg_type is not None
|
|
||||||
if isinstance(analyzed_arg_type, PlaceholderType):
|
|
||||||
unbound = True
|
|
||||||
|
|
||||||
var = Var(name=original_argument.variable.name,
|
|
||||||
type=analyzed_arg_type)
|
|
||||||
var.set_line(original_argument.variable)
|
|
||||||
|
|
||||||
argument = Argument(variable=var,
|
|
||||||
type_annotation=analyzed_arg_type,
|
|
||||||
initializer=original_argument.initializer,
|
|
||||||
kind=original_argument.kind)
|
|
||||||
argument.set_line(original_argument)
|
|
||||||
arguments.append(argument)
|
|
||||||
|
|
||||||
analyzed_ret_type = api.anal_type(get_proper_type(method_type.ret_type), allow_placeholder=True)
|
|
||||||
assert analyzed_ret_type is not None
|
|
||||||
if isinstance(analyzed_ret_type, PlaceholderType):
|
|
||||||
unbound = True
|
|
||||||
return SignatureTuple(arguments, analyzed_ret_type, unbound)
|
|
||||||
|
|
||||||
|
|
||||||
def copy_method_or_incomplete_defn_exception(ctx: ClassDefContext,
|
|
||||||
self_type: Instance,
|
|
||||||
new_method_name: str,
|
|
||||||
method_node: FuncDef) -> None:
|
|
||||||
semanal_api = get_semanal_api(ctx)
|
|
||||||
|
|
||||||
if method_node.type is None:
|
|
||||||
if not semanal_api.final_iteration:
|
|
||||||
raise IncompleteDefnError(f'Unannotated method {method_node.fullname!r}')
|
|
||||||
|
|
||||||
arguments, return_type = prepare_unannotated_method_signature(method_node)
|
|
||||||
add_method(ctx,
|
|
||||||
new_method_name,
|
|
||||||
args=arguments,
|
|
||||||
return_type=return_type,
|
|
||||||
self_type=self_type)
|
|
||||||
return
|
|
||||||
|
|
||||||
assert isinstance(method_node.type, CallableType)
|
|
||||||
|
|
||||||
# copy global SymbolTableNode objects from original class to the current node, if not present
|
|
||||||
original_module = semanal_api.modules[method_node.info.module_name]
|
|
||||||
for name, sym in original_module.names.items():
|
|
||||||
if (not sym.plugin_generated
|
|
||||||
and name not in semanal_api.cur_mod_node.names):
|
|
||||||
semanal_api.add_imported_symbol(name, sym, context=semanal_api.cur_mod_node)
|
|
||||||
|
|
||||||
arguments, analyzed_return_type, unbound = analyze_callable_signature(semanal_api, method_node)
|
|
||||||
assert len(arguments) + 1 == len(method_node.arguments)
|
|
||||||
if unbound:
|
|
||||||
raise IncompleteDefnError(f'Signature of method {method_node.fullname!r} is not ready')
|
|
||||||
|
|
||||||
assert analyzed_return_type is not None
|
|
||||||
|
|
||||||
if new_method_name in ctx.cls.info.names:
|
|
||||||
del ctx.cls.info.names[new_method_name]
|
|
||||||
add_method(ctx,
|
|
||||||
new_method_name,
|
|
||||||
args=arguments,
|
|
||||||
return_type=analyzed_return_type,
|
|
||||||
self_type=self_type)
|
|
||||||
@@ -17,10 +17,10 @@ from mypy_django_plugin.lib import fullnames, helpers
|
|||||||
from mypy_django_plugin.transformers import (
|
from mypy_django_plugin.transformers import (
|
||||||
fields, forms, init_create, meta, querysets, request, settings,
|
fields, forms, init_create, meta, querysets, request, settings,
|
||||||
)
|
)
|
||||||
|
from mypy_django_plugin.transformers.managers import (
|
||||||
|
create_new_manager_class_from_from_queryset_method,
|
||||||
|
)
|
||||||
from mypy_django_plugin.transformers.models import process_model_class
|
from mypy_django_plugin.transformers.models import process_model_class
|
||||||
from mypy_django_plugin.transformers2.dynamic_managers import CreateNewManagerClassFrom_FromQuerySet
|
|
||||||
from mypy_django_plugin.transformers2.models import ModelCallback
|
|
||||||
from mypy_django_plugin.transformers2.related_managers import GetRelatedManagerCallback
|
|
||||||
|
|
||||||
|
|
||||||
def transform_model_class(ctx: ClassDefContext,
|
def transform_model_class(ctx: ClassDefContext,
|
||||||
@@ -123,10 +123,6 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
return 10, module, -1
|
return 10, module, -1
|
||||||
|
|
||||||
def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
|
def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
|
||||||
# load QuerySet and Manager together (for as_manager)
|
|
||||||
if file.fullname == 'django.db.models.query':
|
|
||||||
return [self._new_dependency('django.db.models.manager')]
|
|
||||||
|
|
||||||
# for settings
|
# for settings
|
||||||
if file.fullname == 'django.conf' and self.django_context.django_settings_module:
|
if file.fullname == 'django.conf' and self.django_context.django_settings_module:
|
||||||
return [self._new_dependency(self.django_context.django_settings_module)]
|
return [self._new_dependency(self.django_context.django_settings_module)]
|
||||||
@@ -175,12 +171,16 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
if fullname == 'django.contrib.auth.get_user_model':
|
if fullname == 'django.contrib.auth.get_user_model':
|
||||||
return partial(settings.get_user_model_hook, django_context=self.django_context)
|
return partial(settings.get_user_model_hook, django_context=self.django_context)
|
||||||
|
|
||||||
|
manager_bases = self._get_current_manager_bases()
|
||||||
|
if fullname in manager_bases:
|
||||||
|
return querysets.determine_proper_manager_type
|
||||||
|
|
||||||
info = self._get_typeinfo_or_none(fullname)
|
info = self._get_typeinfo_or_none(fullname)
|
||||||
if info:
|
if info:
|
||||||
if info.has_base(fullnames.FIELD_FULLNAME):
|
if info.has_base(fullnames.FIELD_FULLNAME):
|
||||||
return partial(fields.transform_into_proper_return_type, django_context=self.django_context)
|
return partial(fields.transform_into_proper_return_type, django_context=self.django_context)
|
||||||
|
|
||||||
if helpers.is_subclass_of_model(info, self.django_context):
|
if helpers.is_model_subclass_info(info, self.django_context):
|
||||||
return partial(init_create.redefine_and_typecheck_model_init, django_context=self.django_context)
|
return partial(init_create.redefine_and_typecheck_model_init, django_context=self.django_context)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -224,7 +224,7 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
) -> Optional[Callable[[ClassDefContext], None]]:
|
) -> Optional[Callable[[ClassDefContext], None]]:
|
||||||
if (fullname in self.django_context.all_registered_model_class_fullnames
|
if (fullname in self.django_context.all_registered_model_class_fullnames
|
||||||
or fullname in self._get_current_model_bases()):
|
or fullname in self._get_current_model_bases()):
|
||||||
return ModelCallback(self)
|
return partial(transform_model_class, django_context=self.django_context)
|
||||||
|
|
||||||
if fullname in self._get_current_manager_bases():
|
if fullname in self._get_current_manager_bases():
|
||||||
return add_new_manager_base
|
return add_new_manager_base
|
||||||
@@ -243,10 +243,6 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
info = self._get_typeinfo_or_none(class_name)
|
info = self._get_typeinfo_or_none(class_name)
|
||||||
if info and info.has_base(fullnames.HTTPREQUEST_CLASS_FULLNAME) and attr_name == 'user':
|
if info and info.has_base(fullnames.HTTPREQUEST_CLASS_FULLNAME) and attr_name == 'user':
|
||||||
return partial(request.set_auth_user_model_as_type_for_request_user, django_context=self.django_context)
|
return partial(request.set_auth_user_model_as_type_for_request_user, django_context=self.django_context)
|
||||||
|
|
||||||
if info and info.has_base(fullnames.MODEL_CLASS_FULLNAME):
|
|
||||||
return GetRelatedManagerCallback(self)
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_dynamic_class_hook(self, fullname: str
|
def get_dynamic_class_hook(self, fullname: str
|
||||||
@@ -255,8 +251,7 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
class_name, _, _ = fullname.rpartition('.')
|
class_name, _, _ = fullname.rpartition('.')
|
||||||
info = self._get_typeinfo_or_none(class_name)
|
info = self._get_typeinfo_or_none(class_name)
|
||||||
if info and info.has_base(fullnames.BASE_MANAGER_CLASS_FULLNAME):
|
if info and info.has_base(fullnames.BASE_MANAGER_CLASS_FULLNAME):
|
||||||
return CreateNewManagerClassFrom_FromQuerySet(self)
|
return create_new_manager_class_from_from_queryset_method
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ from mypy.types import Type as MypyType
|
|||||||
from mypy.types import TypeOfAny
|
from mypy.types import TypeOfAny
|
||||||
|
|
||||||
from mypy_django_plugin.django.context import DjangoContext
|
from mypy_django_plugin.django.context import DjangoContext
|
||||||
from mypy_django_plugin.lib import chk_helpers, fullnames, helpers
|
from mypy_django_plugin.lib import fullnames, helpers
|
||||||
|
|
||||||
|
|
||||||
def _get_current_field_from_assignment(ctx: FunctionContext, django_context: DjangoContext) -> Optional[Field]:
|
def _get_current_field_from_assignment(ctx: FunctionContext, django_context: DjangoContext) -> Optional[Field]:
|
||||||
outer_model_info = chk_helpers.get_typechecker_api(ctx).scope.active_class()
|
outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
|
||||||
if (outer_model_info is None
|
if (outer_model_info is None
|
||||||
or not helpers.is_subclass_of_model(outer_model_info, django_context)):
|
or not helpers.is_model_subclass_info(outer_model_info, django_context)):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
field_name = None
|
field_name = None
|
||||||
@@ -66,12 +66,12 @@ def fill_descriptor_types_for_related_field(ctx: FunctionContext, django_context
|
|||||||
# __get__/__set__ of ForeignKey of derived model
|
# __get__/__set__ of ForeignKey of derived model
|
||||||
for model_cls in django_context.all_registered_model_classes:
|
for model_cls in django_context.all_registered_model_classes:
|
||||||
if issubclass(model_cls, current_model_cls) and not model_cls._meta.abstract:
|
if issubclass(model_cls, current_model_cls) and not model_cls._meta.abstract:
|
||||||
derived_model_info = helpers.lookup_class_typeinfo(chk_helpers.get_typechecker_api(ctx), model_cls)
|
derived_model_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), model_cls)
|
||||||
if derived_model_info is not None:
|
if derived_model_info is not None:
|
||||||
fk_ref_type = Instance(derived_model_info, [])
|
fk_ref_type = Instance(derived_model_info, [])
|
||||||
derived_fk_type = reparametrize_related_field_type(default_related_field_type,
|
derived_fk_type = reparametrize_related_field_type(default_related_field_type,
|
||||||
set_type=fk_ref_type, get_type=fk_ref_type)
|
set_type=fk_ref_type, get_type=fk_ref_type)
|
||||||
chk_helpers.add_new_sym_for_info(derived_model_info,
|
helpers.add_new_sym_for_info(derived_model_info,
|
||||||
name=current_field.name,
|
name=current_field.name,
|
||||||
sym_type=derived_fk_type)
|
sym_type=derived_fk_type)
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ def fill_descriptor_types_for_related_field(ctx: FunctionContext, django_context
|
|||||||
if related_model_to_set._meta.proxy_for_model is not None:
|
if related_model_to_set._meta.proxy_for_model is not None:
|
||||||
related_model_to_set = related_model_to_set._meta.proxy_for_model
|
related_model_to_set = related_model_to_set._meta.proxy_for_model
|
||||||
|
|
||||||
typechecker_api = chk_helpers.get_typechecker_api(ctx)
|
typechecker_api = helpers.get_typechecker_api(ctx)
|
||||||
|
|
||||||
related_model_info = helpers.lookup_class_typeinfo(typechecker_api, related_model)
|
related_model_info = helpers.lookup_class_typeinfo(typechecker_api, related_model)
|
||||||
if related_model_info is None:
|
if related_model_info is None:
|
||||||
@@ -110,16 +110,11 @@ def get_field_descriptor_types(field_info: TypeInfo, is_nullable: bool) -> Tuple
|
|||||||
return set_type, get_type
|
return set_type, get_type
|
||||||
|
|
||||||
|
|
||||||
def get_field_type(field_info: TypeInfo, is_nullable: bool) -> Instance:
|
|
||||||
set_type, get_type = get_field_descriptor_types(field_info, is_nullable)
|
|
||||||
return Instance(field_info, [set_type, get_type])
|
|
||||||
|
|
||||||
|
|
||||||
def set_descriptor_types_for_field(ctx: FunctionContext) -> Instance:
|
def set_descriptor_types_for_field(ctx: FunctionContext) -> Instance:
|
||||||
default_return_type = cast(Instance, ctx.default_return_type)
|
default_return_type = cast(Instance, ctx.default_return_type)
|
||||||
|
|
||||||
is_nullable = False
|
is_nullable = False
|
||||||
null_expr = chk_helpers.get_call_argument_by_name(ctx, 'null')
|
null_expr = helpers.get_call_argument_by_name(ctx, 'null')
|
||||||
if null_expr is not None:
|
if null_expr is not None:
|
||||||
is_nullable = helpers.parse_bool(null_expr) or False
|
is_nullable = helpers.parse_bool(null_expr) or False
|
||||||
|
|
||||||
@@ -127,10 +122,10 @@ def set_descriptor_types_for_field(ctx: FunctionContext) -> Instance:
|
|||||||
return helpers.reparametrize_instance(default_return_type, [set_type, get_type])
|
return helpers.reparametrize_instance(default_return_type, [set_type, get_type])
|
||||||
|
|
||||||
|
|
||||||
def determine_type_of_array_field(ctx: FunctionContext) -> MypyType:
|
def determine_type_of_array_field(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
|
||||||
default_return_type = set_descriptor_types_for_field(ctx)
|
default_return_type = set_descriptor_types_for_field(ctx)
|
||||||
|
|
||||||
base_field_arg_type = chk_helpers.get_call_argument_type_by_name(ctx, 'base_field')
|
base_field_arg_type = helpers.get_call_argument_type_by_name(ctx, 'base_field')
|
||||||
if not base_field_arg_type or not isinstance(base_field_arg_type, Instance):
|
if not base_field_arg_type or not isinstance(base_field_arg_type, Instance):
|
||||||
return default_return_type
|
return default_return_type
|
||||||
|
|
||||||
@@ -146,9 +141,9 @@ def transform_into_proper_return_type(ctx: FunctionContext, django_context: Djan
|
|||||||
default_return_type = ctx.default_return_type
|
default_return_type = ctx.default_return_type
|
||||||
assert isinstance(default_return_type, Instance)
|
assert isinstance(default_return_type, Instance)
|
||||||
|
|
||||||
outer_model_info = chk_helpers.get_typechecker_api(ctx).scope.active_class()
|
outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
|
||||||
if (outer_model_info is None
|
if (outer_model_info is None
|
||||||
or not helpers.is_subclass_of_model(outer_model_info, django_context)):
|
or not helpers.is_model_subclass_info(outer_model_info, django_context)):
|
||||||
return ctx.default_return_type
|
return ctx.default_return_type
|
||||||
|
|
||||||
assert isinstance(outer_model_info, TypeInfo)
|
assert isinstance(outer_model_info, TypeInfo)
|
||||||
@@ -157,6 +152,6 @@ def transform_into_proper_return_type(ctx: FunctionContext, django_context: Djan
|
|||||||
return fill_descriptor_types_for_related_field(ctx, django_context)
|
return fill_descriptor_types_for_related_field(ctx, django_context)
|
||||||
|
|
||||||
if default_return_type.type.has_base(fullnames.ARRAY_FIELD_FULLNAME):
|
if default_return_type.type.has_base(fullnames.ARRAY_FIELD_FULLNAME):
|
||||||
return determine_type_of_array_field(ctx)
|
return determine_type_of_array_field(ctx, django_context)
|
||||||
|
|
||||||
return set_descriptor_types_for_field(ctx)
|
return set_descriptor_types_for_field(ctx)
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ from mypy.types import CallableType, Instance, NoneTyp
|
|||||||
from mypy.types import Type as MypyType
|
from mypy.types import Type as MypyType
|
||||||
from mypy.types import TypeType
|
from mypy.types import TypeType
|
||||||
|
|
||||||
from mypy_django_plugin.lib import chk_helpers, sem_helpers
|
from mypy_django_plugin.lib import helpers
|
||||||
|
|
||||||
|
|
||||||
def make_meta_nested_class_inherit_from_any(ctx: ClassDefContext) -> None:
|
def make_meta_nested_class_inherit_from_any(ctx: ClassDefContext) -> None:
|
||||||
meta_node = sem_helpers.get_nested_meta_node_for_current_class(ctx.cls.info)
|
meta_node = helpers.get_nested_meta_node_for_current_class(ctx.cls.info)
|
||||||
if meta_node is None:
|
if meta_node is None:
|
||||||
if not ctx.api.final_iteration:
|
if not ctx.api.final_iteration:
|
||||||
ctx.api.defer()
|
ctx.api.defer()
|
||||||
@@ -28,7 +28,7 @@ def extract_proper_type_for_get_form(ctx: MethodContext) -> MypyType:
|
|||||||
object_type = ctx.type
|
object_type = ctx.type
|
||||||
assert isinstance(object_type, Instance)
|
assert isinstance(object_type, Instance)
|
||||||
|
|
||||||
form_class_type = chk_helpers.get_call_argument_type_by_name(ctx, 'form_class')
|
form_class_type = helpers.get_call_argument_type_by_name(ctx, 'form_class')
|
||||||
if form_class_type is None or isinstance(form_class_type, NoneTyp):
|
if form_class_type is None or isinstance(form_class_type, NoneTyp):
|
||||||
form_class_type = get_specified_form_class(object_type)
|
form_class_type = get_specified_form_class(object_type)
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from mypy.types import Instance
|
|||||||
from mypy.types import Type as MypyType
|
from mypy.types import Type as MypyType
|
||||||
|
|
||||||
from mypy_django_plugin.django.context import DjangoContext
|
from mypy_django_plugin.django.context import DjangoContext
|
||||||
from mypy_django_plugin.lib import chk_helpers
|
from mypy_django_plugin.lib import helpers
|
||||||
|
|
||||||
|
|
||||||
def get_actual_types(ctx: Union[MethodContext, FunctionContext],
|
def get_actual_types(ctx: Union[MethodContext, FunctionContext],
|
||||||
@@ -32,7 +32,7 @@ def get_actual_types(ctx: Union[MethodContext, FunctionContext],
|
|||||||
|
|
||||||
def typecheck_model_method(ctx: Union[FunctionContext, MethodContext], django_context: DjangoContext,
|
def typecheck_model_method(ctx: Union[FunctionContext, MethodContext], django_context: DjangoContext,
|
||||||
model_cls: Type[Model], method: str) -> MypyType:
|
model_cls: Type[Model], method: str) -> MypyType:
|
||||||
typechecker_api = chk_helpers.get_typechecker_api(ctx)
|
typechecker_api = helpers.get_typechecker_api(ctx)
|
||||||
expected_types = django_context.get_expected_types(typechecker_api, model_cls, method=method)
|
expected_types = django_context.get_expected_types(typechecker_api, model_cls, method=method)
|
||||||
expected_keys = [key for key in expected_types.keys() if key != 'pk']
|
expected_keys = [key for key in expected_types.keys() if key != 'pk']
|
||||||
|
|
||||||
@@ -42,11 +42,11 @@ def typecheck_model_method(ctx: Union[FunctionContext, MethodContext], django_co
|
|||||||
model_cls.__name__),
|
model_cls.__name__),
|
||||||
ctx.context)
|
ctx.context)
|
||||||
continue
|
continue
|
||||||
error_message = 'Incompatible type for "{}" of "{}"'.format(actual_name, model_cls.__name__)
|
helpers.check_types_compatible(ctx,
|
||||||
chk_helpers.check_types_compatible(ctx,
|
|
||||||
expected_type=expected_types[actual_name],
|
expected_type=expected_types[actual_name],
|
||||||
actual_type=actual_type,
|
actual_type=actual_type,
|
||||||
error_message=error_message)
|
error_message='Incompatible type for "{}" of "{}"'.format(actual_name,
|
||||||
|
model_cls.__name__))
|
||||||
|
|
||||||
return ctx.default_return_type
|
return ctx.default_return_type
|
||||||
|
|
||||||
|
|||||||
@@ -1,232 +1,77 @@
|
|||||||
from typing import Any, Dict, Iterator, Optional, Tuple
|
|
||||||
|
|
||||||
from mypy.checker import gen_unique_name
|
|
||||||
from mypy.nodes import (
|
from mypy.nodes import (
|
||||||
GDEF, CallExpr, Context, Decorator, FuncDef, MemberExpr, NameExpr, OverloadedFuncDef, PlaceholderNode, RefExpr,
|
GDEF, FuncDef, MemberExpr, NameExpr, RefExpr, StrExpr, SymbolTableNode, TypeInfo,
|
||||||
StrExpr, SymbolTable, SymbolTableNode, TypeInfo,
|
)
|
||||||
MypyFile)
|
from mypy.plugin import ClassDefContext, DynamicClassDefContext
|
||||||
from mypy.plugin import ClassDefContext, DynamicClassDefContext, MethodContext
|
from mypy.types import AnyType, Instance, TypeOfAny
|
||||||
from mypy.semanal import SemanticAnalyzer, is_same_symbol, is_valid_replacement
|
|
||||||
from mypy.types import AnyType, CallableType, Instance, TypeVarType, TypeVarDef
|
|
||||||
from mypy.types import Type as MypyType
|
|
||||||
from mypy.types import TypeOfAny
|
|
||||||
|
|
||||||
from mypy_django_plugin.lib import chk_helpers, fullnames, helpers, sem_helpers
|
from mypy_django_plugin.lib import fullnames, helpers
|
||||||
|
|
||||||
|
|
||||||
def iter_all_custom_queryset_methods(derived_queryset_info: TypeInfo) -> Iterator[Tuple[str, FuncDef]]:
|
|
||||||
for base_queryset_info in derived_queryset_info.mro:
|
|
||||||
if base_queryset_info.fullname == fullnames.QUERYSET_CLASS_FULLNAME:
|
|
||||||
break
|
|
||||||
for name, sym in base_queryset_info.names.items():
|
|
||||||
if isinstance(sym.node, FuncDef):
|
|
||||||
yield name, sym.node
|
|
||||||
|
|
||||||
|
|
||||||
def generate_from_queryset_name(base_manager_info: TypeInfo, queryset_info: TypeInfo) -> str:
|
|
||||||
return base_manager_info.name + 'From' + queryset_info.name
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# def cb_resolve_callee_info_or_exception(cb: ) -> TypeInfo:
|
|
||||||
# callee = ctx.call.callee
|
|
||||||
# assert isinstance(callee, MemberExpr)
|
|
||||||
# assert isinstance(callee.expr, RefExpr)
|
|
||||||
#
|
|
||||||
# callee_info = callee.expr.node
|
|
||||||
# if (callee_info is None
|
|
||||||
# or isinstance(callee_info, PlaceholderNode)):
|
|
||||||
# raise sem_helpers.IncompleteDefnError(f'Definition of base manager {callee.fullname!r} '
|
|
||||||
# f'is incomplete.')
|
|
||||||
#
|
|
||||||
# assert isinstance(callee_info, TypeInfo)
|
|
||||||
# return callee_info
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_callee_info_or_exception(callback: helpers.DynamicClassPluginCallback) -> TypeInfo:
|
|
||||||
callee = callback.call_expr.callee
|
|
||||||
assert isinstance(callee, MemberExpr)
|
|
||||||
assert isinstance(callee.expr, RefExpr)
|
|
||||||
|
|
||||||
callee_info = callee.expr.node
|
|
||||||
if (callee_info is None
|
|
||||||
or isinstance(callee_info, PlaceholderNode)):
|
|
||||||
raise sem_helpers.IncompleteDefnError(f'Definition of base manager {callee.fullname!r} '
|
|
||||||
f'is incomplete.')
|
|
||||||
|
|
||||||
assert isinstance(callee_info, TypeInfo)
|
|
||||||
return callee_info
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_passed_queryset_info_or_exception(callback: helpers.DynamicClassPluginCallback) -> TypeInfo:
|
|
||||||
passed_queryset_name_expr = callback.call_expr.args[0]
|
|
||||||
assert isinstance(passed_queryset_name_expr, NameExpr)
|
|
||||||
|
|
||||||
# lookup in the same module
|
|
||||||
sym = callback.semanal_api.lookup_qualified(passed_queryset_name_expr.name, ctx=callback.call_expr)
|
|
||||||
if (sym is None
|
|
||||||
or sym.node is None
|
|
||||||
or isinstance(sym.node, PlaceholderNode)):
|
|
||||||
bound_name = passed_queryset_name_expr.fullname or passed_queryset_name_expr.name
|
|
||||||
raise sem_helpers.BoundNameNotFound(bound_name)
|
|
||||||
|
|
||||||
assert isinstance(sym.node, TypeInfo)
|
|
||||||
return sym.node
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_django_manager_info_or_exception(callback: helpers.DynamicClassPluginCallback) -> TypeInfo:
|
|
||||||
info = callback.lookup_typeinfo_or_defer(fullnames.MANAGER_CLASS_FULLNAME)
|
|
||||||
if info is None:
|
|
||||||
raise sem_helpers.BoundNameNotFound(fullnames.MANAGER_CLASS_FULLNAME)
|
|
||||||
|
|
||||||
return info
|
|
||||||
|
|
||||||
|
|
||||||
def new_manager_typeinfo(ctx: DynamicClassDefContext, callee_manager_info: TypeInfo) -> TypeInfo:
|
|
||||||
callee_manager_type = Instance(callee_manager_info, [AnyType(TypeOfAny.unannotated)])
|
|
||||||
api = sem_helpers.get_semanal_api(ctx)
|
|
||||||
|
|
||||||
new_manager_class_name = ctx.name
|
|
||||||
new_manager_info = helpers.new_typeinfo(new_manager_class_name,
|
|
||||||
bases=[callee_manager_type], module_name=api.cur_mod_id)
|
|
||||||
new_manager_info.set_line(ctx.call)
|
|
||||||
return new_manager_info
|
|
||||||
|
|
||||||
|
|
||||||
def get_generated_manager_fullname(call: CallExpr, base_manager_info: TypeInfo, queryset_info: TypeInfo) -> str:
|
|
||||||
if len(call.args) > 1:
|
|
||||||
# only for from_queryset()
|
|
||||||
expr = call.args[1]
|
|
||||||
assert isinstance(expr, StrExpr)
|
|
||||||
custom_manager_generated_name = expr.value
|
|
||||||
else:
|
|
||||||
custom_manager_generated_name = base_manager_info.name + 'From' + queryset_info.name
|
|
||||||
|
|
||||||
custom_manager_generated_fullname = 'django.db.models.manager' + '.' + custom_manager_generated_name
|
|
||||||
return custom_manager_generated_fullname
|
|
||||||
|
|
||||||
|
|
||||||
def get_generated_managers_metadata(django_manager_info: TypeInfo) -> Dict[str, Any]:
|
|
||||||
return django_manager_info.metadata.setdefault('from_queryset_managers', {})
|
|
||||||
|
|
||||||
|
|
||||||
def record_new_manager_info_fullname_into_metadata(ctx: DynamicClassDefContext,
|
|
||||||
new_manager_fullname: str,
|
|
||||||
callee_manager_info: TypeInfo,
|
|
||||||
queryset_info: TypeInfo,
|
|
||||||
django_manager_info: TypeInfo) -> None:
|
|
||||||
custom_manager_generated_fullname = get_generated_manager_fullname(ctx.call,
|
|
||||||
base_manager_info=callee_manager_info,
|
|
||||||
queryset_info=queryset_info)
|
|
||||||
metadata = get_generated_managers_metadata(django_manager_info)
|
|
||||||
metadata[custom_manager_generated_fullname] = new_manager_fullname
|
|
||||||
|
|
||||||
|
|
||||||
def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefContext) -> None:
|
def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefContext) -> None:
|
||||||
semanal_api = sem_helpers.get_semanal_api(ctx)
|
semanal_api = helpers.get_semanal_api(ctx)
|
||||||
try:
|
|
||||||
callee_manager_info = resolve_callee_info_or_exception(ctx)
|
callee = ctx.call.callee
|
||||||
queryset_info = resolve_passed_queryset_info_or_exception(ctx)
|
assert isinstance(callee, MemberExpr)
|
||||||
django_manager_info = resolve_django_manager_info_or_exception(ctx)
|
assert isinstance(callee.expr, RefExpr)
|
||||||
except sem_helpers.IncompleteDefnError:
|
|
||||||
|
base_manager_info = callee.expr.node
|
||||||
|
if base_manager_info is None:
|
||||||
if not semanal_api.final_iteration:
|
if not semanal_api.final_iteration:
|
||||||
semanal_api.defer()
|
semanal_api.defer()
|
||||||
return
|
return
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
new_manager_info = new_manager_typeinfo(ctx, callee_manager_info)
|
assert isinstance(base_manager_info, TypeInfo)
|
||||||
record_new_manager_info_fullname_into_metadata(ctx,
|
new_manager_info = semanal_api.basic_new_typeinfo(ctx.name,
|
||||||
new_manager_info.fullname,
|
basetype_or_fallback=Instance(base_manager_info,
|
||||||
callee_manager_info,
|
[AnyType(TypeOfAny.unannotated)]))
|
||||||
queryset_info,
|
new_manager_info.line = ctx.call.line
|
||||||
django_manager_info)
|
new_manager_info.defn.line = ctx.call.line
|
||||||
|
new_manager_info.metaclass_type = new_manager_info.calculate_metaclass_type()
|
||||||
|
|
||||||
|
current_module = semanal_api.cur_mod_node
|
||||||
|
current_module.names[ctx.name] = SymbolTableNode(GDEF, new_manager_info,
|
||||||
|
plugin_generated=True)
|
||||||
|
passed_queryset = ctx.call.args[0]
|
||||||
|
assert isinstance(passed_queryset, NameExpr)
|
||||||
|
|
||||||
|
derived_queryset_fullname = passed_queryset.fullname
|
||||||
|
assert derived_queryset_fullname is not None
|
||||||
|
|
||||||
|
sym = semanal_api.lookup_fully_qualified_or_none(derived_queryset_fullname)
|
||||||
|
assert sym is not None
|
||||||
|
if sym.node is None:
|
||||||
|
if not semanal_api.final_iteration:
|
||||||
|
semanal_api.defer()
|
||||||
|
else:
|
||||||
|
# inherit from Any to prevent false-positives, if queryset class cannot be resolved
|
||||||
|
new_manager_info.fallback_to_any = True
|
||||||
|
return
|
||||||
|
|
||||||
|
derived_queryset_info = sym.node
|
||||||
|
assert isinstance(derived_queryset_info, TypeInfo)
|
||||||
|
|
||||||
|
if len(ctx.call.args) > 1:
|
||||||
|
expr = ctx.call.args[1]
|
||||||
|
assert isinstance(expr, StrExpr)
|
||||||
|
custom_manager_generated_name = expr.value
|
||||||
|
else:
|
||||||
|
custom_manager_generated_name = base_manager_info.name + 'From' + derived_queryset_info.name
|
||||||
|
|
||||||
|
custom_manager_generated_fullname = '.'.join(['django.db.models.manager', custom_manager_generated_name])
|
||||||
|
if 'from_queryset_managers' not in base_manager_info.metadata:
|
||||||
|
base_manager_info.metadata['from_queryset_managers'] = {}
|
||||||
|
base_manager_info.metadata['from_queryset_managers'][custom_manager_generated_fullname] = new_manager_info.fullname
|
||||||
|
|
||||||
class_def_context = ClassDefContext(cls=new_manager_info.defn,
|
class_def_context = ClassDefContext(cls=new_manager_info.defn,
|
||||||
reason=ctx.call, api=semanal_api)
|
reason=ctx.call, api=semanal_api)
|
||||||
self_type = Instance(new_manager_info, [AnyType(TypeOfAny.explicit)])
|
self_type = Instance(new_manager_info, [])
|
||||||
|
# we need to copy all methods in MRO before django.db.models.query.QuerySet
|
||||||
try:
|
for class_mro_info in derived_queryset_info.mro:
|
||||||
for name, method_node in iter_all_custom_queryset_methods(queryset_info):
|
if class_mro_info.fullname == fullnames.QUERYSET_CLASS_FULLNAME:
|
||||||
sem_helpers.copy_method_or_incomplete_defn_exception(class_def_context,
|
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,
|
self_type,
|
||||||
new_method_name=name,
|
new_method_name=name,
|
||||||
method_node=method_node)
|
method_node=sym.node)
|
||||||
except sem_helpers.IncompleteDefnError:
|
|
||||||
if not semanal_api.final_iteration:
|
|
||||||
semanal_api.defer()
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
new_manager_sym = SymbolTableNode(GDEF, new_manager_info, plugin_generated=True)
|
|
||||||
|
|
||||||
# context=None - forcibly replace old node
|
|
||||||
added = semanal_api.add_symbol_table_node(ctx.name, new_manager_sym, context=None)
|
|
||||||
if added:
|
|
||||||
# replace all references to the old manager Var everywhere
|
|
||||||
for _, module in semanal_api.modules.items():
|
|
||||||
if module.fullname != semanal_api.cur_mod_id:
|
|
||||||
for sym_name, sym in module.names.items():
|
|
||||||
if sym.fullname == new_manager_info.fullname:
|
|
||||||
module.names[sym_name] = new_manager_sym.copy()
|
|
||||||
|
|
||||||
# we need another iteration to process methods
|
|
||||||
if (not added
|
|
||||||
and not semanal_api.final_iteration):
|
|
||||||
semanal_api.defer()
|
|
||||||
|
|
||||||
|
|
||||||
def add_symbol_table_node(api: SemanticAnalyzer,
|
|
||||||
name: str,
|
|
||||||
symbol: SymbolTableNode,
|
|
||||||
context: Optional[Context] = None,
|
|
||||||
symbol_table: Optional[SymbolTable] = None,
|
|
||||||
can_defer: bool = True,
|
|
||||||
escape_comprehensions: bool = False) -> bool:
|
|
||||||
"""Add symbol table node to the currently active symbol table.
|
|
||||||
|
|
||||||
Return True if we actually added the symbol, or False if we refused
|
|
||||||
to do so (because something is not ready or it was a no-op).
|
|
||||||
|
|
||||||
Generate an error if there is an invalid redefinition.
|
|
||||||
|
|
||||||
If context is None, unconditionally add node, since we can't report
|
|
||||||
an error. Note that this is used by plugins to forcibly replace nodes!
|
|
||||||
|
|
||||||
TODO: Prevent plugins from replacing nodes, as it could cause problems?
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: short name of symbol
|
|
||||||
symbol: Node to add
|
|
||||||
can_defer: if True, defer current target if adding a placeholder
|
|
||||||
context: error context (see above about None value)
|
|
||||||
"""
|
|
||||||
names = symbol_table or api.current_symbol_table(escape_comprehensions=escape_comprehensions)
|
|
||||||
existing = names.get(name)
|
|
||||||
if isinstance(symbol.node, PlaceholderNode) and can_defer:
|
|
||||||
api.defer(context)
|
|
||||||
if (existing is not None
|
|
||||||
and context is not None
|
|
||||||
and not is_valid_replacement(existing, symbol)):
|
|
||||||
# There is an existing node, so this may be a redefinition.
|
|
||||||
# If the new node points to the same node as the old one,
|
|
||||||
# or if both old and new nodes are placeholders, we don't
|
|
||||||
# need to do anything.
|
|
||||||
old = existing.node
|
|
||||||
new = symbol.node
|
|
||||||
if isinstance(new, PlaceholderNode):
|
|
||||||
# We don't know whether this is okay. Let's wait until the next iteration.
|
|
||||||
return False
|
|
||||||
if not is_same_symbol(old, new):
|
|
||||||
if isinstance(new, (FuncDef, Decorator, OverloadedFuncDef, TypeInfo)):
|
|
||||||
api.add_redefinition(names, name, symbol)
|
|
||||||
if not (isinstance(new, (FuncDef, Decorator))
|
|
||||||
and api.set_original_def(old, new)):
|
|
||||||
api.name_already_defined(name, context, existing)
|
|
||||||
elif name not in api.missing_names and '*' not in api.missing_names:
|
|
||||||
names[name] = symbol
|
|
||||||
api.progress = True
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ from mypy.types import Type as MypyType
|
|||||||
from mypy.types import TypeOfAny
|
from mypy.types import TypeOfAny
|
||||||
|
|
||||||
from mypy_django_plugin.django.context import DjangoContext
|
from mypy_django_plugin.django.context import DjangoContext
|
||||||
from mypy_django_plugin.lib import chk_helpers, helpers
|
from mypy_django_plugin.lib import helpers
|
||||||
|
|
||||||
|
|
||||||
def _get_field_instance(ctx: MethodContext, field_fullname: str) -> MypyType:
|
def _get_field_instance(ctx: MethodContext, field_fullname: str) -> MypyType:
|
||||||
api = chk_helpers.get_typechecker_api(ctx)
|
field_info = helpers.lookup_fully_qualified_typeinfo(helpers.get_typechecker_api(ctx),
|
||||||
field_info = helpers.lookup_fully_qualified_typeinfo(api, field_fullname)
|
field_fullname)
|
||||||
if field_info is None:
|
if field_info is None:
|
||||||
return AnyType(TypeOfAny.unannotated)
|
return AnyType(TypeOfAny.unannotated)
|
||||||
return Instance(field_info, [AnyType(TypeOfAny.explicit), AnyType(TypeOfAny.explicit)])
|
return Instance(field_info, [AnyType(TypeOfAny.explicit), AnyType(TypeOfAny.explicit)])
|
||||||
@@ -32,7 +32,7 @@ def return_proper_field_type_from_get_field(ctx: MethodContext, django_context:
|
|||||||
if model_cls is None:
|
if model_cls is None:
|
||||||
return ctx.default_return_type
|
return ctx.default_return_type
|
||||||
|
|
||||||
field_name_expr = chk_helpers.get_call_argument_by_name(ctx, 'field_name')
|
field_name_expr = helpers.get_call_argument_by_name(ctx, 'field_name')
|
||||||
if field_name_expr is None:
|
if field_name_expr is None:
|
||||||
return ctx.default_return_type
|
return ctx.default_return_type
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
from typing import List, Optional, Type, cast
|
from typing import Dict, List, Optional, Type, cast
|
||||||
|
|
||||||
from django.db.models.base import Model
|
from django.db.models.base import Model
|
||||||
from django.db.models.fields import DateField, DateTimeField
|
from django.db.models.fields import DateField, DateTimeField
|
||||||
from django.db.models.fields.related import ForeignKey, OneToOneField
|
from django.db.models.fields.related import ForeignKey
|
||||||
from django.db.models.fields.reverse_related import (
|
from django.db.models.fields.reverse_related import (
|
||||||
ManyToManyRel, ManyToOneRel, OneToOneRel,
|
ManyToManyRel, ManyToOneRel, OneToOneRel,
|
||||||
)
|
)
|
||||||
from mypy.nodes import (
|
from mypy.nodes import ARG_STAR2, Argument, Context, FuncDef, TypeInfo, Var
|
||||||
ARG_STAR2, GDEF, MDEF, Argument, Context, SymbolTableNode, TypeInfo, Var,
|
|
||||||
)
|
|
||||||
from mypy.plugin import ClassDefContext
|
from mypy.plugin import ClassDefContext
|
||||||
from mypy.plugins import common
|
from mypy.plugins import common
|
||||||
from mypy.semanal import SemanticAnalyzer, dummy_context
|
from mypy.semanal import SemanticAnalyzer
|
||||||
from mypy.types import AnyType, Instance
|
from mypy.types import AnyType, Instance
|
||||||
from mypy.types import Type as MypyType
|
from mypy.types import Type as MypyType
|
||||||
from mypy.types import TypeOfAny
|
from mypy.types import TypeOfAny
|
||||||
|
|
||||||
from mypy_django_plugin.django.context import DjangoContext
|
from mypy_django_plugin.django.context import DjangoContext
|
||||||
from mypy_django_plugin.lib import fullnames, helpers, sem_helpers
|
from mypy_django_plugin.lib import fullnames, helpers
|
||||||
from mypy_django_plugin.transformers import fields
|
from mypy_django_plugin.transformers import fields
|
||||||
from mypy_django_plugin.transformers.fields import get_field_descriptor_types
|
from mypy_django_plugin.transformers.fields import get_field_descriptor_types
|
||||||
|
|
||||||
@@ -34,63 +32,37 @@ class ModelClassInitializer:
|
|||||||
def lookup_typeinfo(self, fullname: str) -> Optional[TypeInfo]:
|
def lookup_typeinfo(self, fullname: str) -> Optional[TypeInfo]:
|
||||||
return helpers.lookup_fully_qualified_typeinfo(self.api, fullname)
|
return helpers.lookup_fully_qualified_typeinfo(self.api, fullname)
|
||||||
|
|
||||||
def lookup_typeinfo_or_exception(self, fullname: str) -> TypeInfo:
|
def lookup_typeinfo_or_incomplete_defn_error(self, fullname: str) -> TypeInfo:
|
||||||
info = self.lookup_typeinfo(fullname)
|
info = self.lookup_typeinfo(fullname)
|
||||||
if info is None:
|
if info is None:
|
||||||
raise sem_helpers.IncompleteDefnError(f'No {fullname!r} found')
|
raise helpers.IncompleteDefnException(f'No {fullname!r} found')
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def lookup_class_typeinfo_or_exception(self, klass: type) -> TypeInfo:
|
def lookup_class_typeinfo_or_incomplete_defn_error(self, klass: type) -> TypeInfo:
|
||||||
fullname = helpers.get_class_fullname(klass)
|
fullname = helpers.get_class_fullname(klass)
|
||||||
field_info = self.lookup_typeinfo_or_exception(fullname)
|
field_info = self.lookup_typeinfo_or_incomplete_defn_error(fullname)
|
||||||
return field_info
|
return field_info
|
||||||
|
|
||||||
def model_class_has_attribute_defined(self, name: str, traverse_mro: bool = True) -> bool:
|
def create_new_var(self, name: str, typ: MypyType) -> Var:
|
||||||
if not traverse_mro:
|
# type=: type of the variable itself
|
||||||
sym = self.model_classdef.info.names.get(name)
|
var = Var(name=name, type=typ)
|
||||||
else:
|
# var.info: type of the object variable is bound to
|
||||||
sym = self.model_classdef.info.get(name)
|
|
||||||
return sym is not None
|
|
||||||
|
|
||||||
def resolve_manager_fullname(self, manager_fullname: str) -> str:
|
|
||||||
base_manager_info = self.lookup_typeinfo(fullnames.MANAGER_CLASS_FULLNAME)
|
|
||||||
if (base_manager_info is None
|
|
||||||
or 'from_queryset_managers' not in base_manager_info.metadata):
|
|
||||||
return manager_fullname
|
|
||||||
|
|
||||||
metadata = base_manager_info.metadata['from_queryset_managers']
|
|
||||||
return metadata.get(manager_fullname, manager_fullname)
|
|
||||||
|
|
||||||
def add_new_node_to_model_class(self, name: str, typ: MypyType,
|
|
||||||
force_replace_existing: bool = False) -> None:
|
|
||||||
if not force_replace_existing and name in self.model_classdef.info.names:
|
|
||||||
raise ValueError(f'Member {name!r} already defined at model {self.model_classdef.info.fullname!r}.')
|
|
||||||
|
|
||||||
var = Var(name, type=typ)
|
|
||||||
# TypeInfo of the object variable is bound to
|
|
||||||
var.info = self.model_classdef.info
|
var.info = self.model_classdef.info
|
||||||
var._fullname = self.api.qualified_name(name)
|
var._fullname = self.model_classdef.info.fullname + '.' + name
|
||||||
var.is_initialized_in_class = True
|
var.is_initialized_in_class = True
|
||||||
|
var.is_inferred = True
|
||||||
|
return var
|
||||||
|
|
||||||
sym = SymbolTableNode(MDEF, var, plugin_generated=True)
|
def add_new_node_to_model_class(self, name: str, typ: MypyType) -> None:
|
||||||
context: Optional[Context] = dummy_context()
|
helpers.add_new_sym_for_info(self.model_classdef.info,
|
||||||
if force_replace_existing:
|
name=name,
|
||||||
context = None
|
sym_type=typ)
|
||||||
self.api.add_symbol_table_node(name, sym, context=context)
|
|
||||||
|
|
||||||
def add_new_class_for_current_module(self, name: str, bases: List[Instance],
|
def add_new_class_for_current_module(self, name: str, bases: List[Instance]) -> TypeInfo:
|
||||||
force_replace_existing: bool = False) -> TypeInfo:
|
current_module = self.api.modules[self.model_classdef.info.module_name]
|
||||||
current_module = self.api.cur_mod_node
|
new_class_info = helpers.add_new_class_for_module(current_module,
|
||||||
if not force_replace_existing and name in current_module.names:
|
name=name, bases=bases)
|
||||||
raise ValueError(f'Class {name!r} already defined for module {current_module.fullname!r}')
|
return new_class_info
|
||||||
|
|
||||||
new_typeinfo = helpers.new_typeinfo(name,
|
|
||||||
bases=bases,
|
|
||||||
module_name=current_module.fullname)
|
|
||||||
if name in current_module.names:
|
|
||||||
del current_module.names[name]
|
|
||||||
current_module.names[name] = SymbolTableNode(GDEF, new_typeinfo, plugin_generated=True)
|
|
||||||
return new_typeinfo
|
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
model_cls = self.django_context.get_model_class_by_fullname(self.model_classdef.fullname)
|
model_cls = self.django_context.get_model_class_by_fullname(self.model_classdef.fullname)
|
||||||
@@ -116,59 +88,29 @@ class InjectAnyAsBaseForNestedMeta(ModelClassInitializer):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
meta_node = sem_helpers.get_nested_meta_node_for_current_class(self.model_classdef.info)
|
meta_node = helpers.get_nested_meta_node_for_current_class(self.model_classdef.info)
|
||||||
if meta_node is None:
|
if meta_node is None:
|
||||||
return None
|
return None
|
||||||
meta_node.fallback_to_any = True
|
meta_node.fallback_to_any = True
|
||||||
|
|
||||||
|
|
||||||
class AddDefaultPrimaryKey(ModelClassInitializer):
|
class AddDefaultPrimaryKey(ModelClassInitializer):
|
||||||
"""
|
|
||||||
Adds default primary key to models which does not define their own.
|
|
||||||
```
|
|
||||||
class User(models.Model):
|
|
||||||
name = models.TextField()
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
|
|
||||||
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
||||||
auto_field = model_cls._meta.auto_field
|
auto_field = model_cls._meta.auto_field
|
||||||
if auto_field is None:
|
if auto_field and not self.model_classdef.info.has_readable_member(auto_field.attname):
|
||||||
return
|
# autogenerated field
|
||||||
|
auto_field_fullname = helpers.get_class_fullname(auto_field.__class__)
|
||||||
primary_key_attrname = auto_field.attname
|
auto_field_info = self.lookup_typeinfo_or_incomplete_defn_error(auto_field_fullname)
|
||||||
if self.model_class_has_attribute_defined(primary_key_attrname):
|
|
||||||
return
|
|
||||||
|
|
||||||
auto_field_class_fullname = helpers.get_class_fullname(auto_field.__class__)
|
|
||||||
auto_field_info = self.lookup_typeinfo_or_exception(auto_field_class_fullname)
|
|
||||||
|
|
||||||
set_type, get_type = fields.get_field_descriptor_types(auto_field_info, is_nullable=False)
|
set_type, get_type = fields.get_field_descriptor_types(auto_field_info, is_nullable=False)
|
||||||
self.add_new_node_to_model_class(primary_key_attrname, Instance(auto_field_info,
|
self.add_new_node_to_model_class(auto_field.attname, Instance(auto_field_info,
|
||||||
[set_type, get_type]))
|
[set_type, get_type]))
|
||||||
|
|
||||||
|
|
||||||
class AddRelatedModelsId(ModelClassInitializer):
|
class AddRelatedModelsId(ModelClassInitializer):
|
||||||
"""
|
|
||||||
Adds `FIELDNAME_id` attributes to models.
|
|
||||||
```
|
|
||||||
class User(models.Model):
|
|
||||||
pass
|
|
||||||
class Blog(models.Model):
|
|
||||||
user = models.ForeignKey(User)
|
|
||||||
```
|
|
||||||
|
|
||||||
`user_id` will be added to `Blog`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
||||||
for field in model_cls._meta.get_fields():
|
for field in model_cls._meta.get_fields():
|
||||||
if not isinstance(field, (OneToOneField, ForeignKey)):
|
if isinstance(field, ForeignKey):
|
||||||
continue
|
|
||||||
related_id_attr_name = field.attname
|
|
||||||
if self.model_class_has_attribute_defined(related_id_attr_name):
|
|
||||||
continue
|
|
||||||
|
|
||||||
related_model_cls = self.django_context.get_field_related_model_cls(field)
|
related_model_cls = self.django_context.get_field_related_model_cls(field)
|
||||||
if related_model_cls is None:
|
if related_model_cls is None:
|
||||||
error_context: Context = self.ctx.cls
|
error_context: Context = self.ctx.cls
|
||||||
@@ -178,7 +120,7 @@ class AddRelatedModelsId(ModelClassInitializer):
|
|||||||
self.api.fail(f'Cannot find model {field.related_model!r} '
|
self.api.fail(f'Cannot find model {field.related_model!r} '
|
||||||
f'referenced in field {field.name!r} ',
|
f'referenced in field {field.name!r} ',
|
||||||
ctx=error_context)
|
ctx=error_context)
|
||||||
self.add_new_node_to_model_class(related_id_attr_name,
|
self.add_new_node_to_model_class(field.attname,
|
||||||
AnyType(TypeOfAny.explicit))
|
AnyType(TypeOfAny.explicit))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -187,8 +129,8 @@ class AddRelatedModelsId(ModelClassInitializer):
|
|||||||
|
|
||||||
rel_primary_key_field = self.django_context.get_primary_key_field(related_model_cls)
|
rel_primary_key_field = self.django_context.get_primary_key_field(related_model_cls)
|
||||||
try:
|
try:
|
||||||
field_info = self.lookup_class_typeinfo_or_exception(rel_primary_key_field.__class__)
|
field_info = self.lookup_class_typeinfo_or_incomplete_defn_error(rel_primary_key_field.__class__)
|
||||||
except sem_helpers.IncompleteDefnError as exc:
|
except helpers.IncompleteDefnException as exc:
|
||||||
if not self.api.final_iteration:
|
if not self.api.final_iteration:
|
||||||
raise exc
|
raise exc
|
||||||
else:
|
else:
|
||||||
@@ -196,53 +138,109 @@ class AddRelatedModelsId(ModelClassInitializer):
|
|||||||
|
|
||||||
is_nullable = self.django_context.get_field_nullability(field, None)
|
is_nullable = self.django_context.get_field_nullability(field, None)
|
||||||
set_type, get_type = get_field_descriptor_types(field_info, is_nullable)
|
set_type, get_type = get_field_descriptor_types(field_info, is_nullable)
|
||||||
self.add_new_node_to_model_class(related_id_attr_name,
|
self.add_new_node_to_model_class(field.attname,
|
||||||
Instance(field_info, [set_type, get_type]))
|
Instance(field_info, [set_type, get_type]))
|
||||||
|
|
||||||
|
|
||||||
class AddManagers(ModelClassInitializer):
|
class AddManagers(ModelClassInitializer):
|
||||||
|
def has_any_parametrized_manager_as_base(self, info: TypeInfo) -> bool:
|
||||||
|
for base in helpers.iter_bases(info):
|
||||||
|
if self.is_any_parametrized_manager(base):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_any_parametrized_manager(self, typ: Instance) -> bool:
|
||||||
|
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)
|
||||||
|
if (base_manager_info is None
|
||||||
|
or 'from_queryset_managers' not in base_manager_info.metadata):
|
||||||
|
return {}
|
||||||
|
return base_manager_info.metadata['from_queryset_managers']
|
||||||
|
|
||||||
|
def create_new_model_parametrized_manager(self, name: str, base_manager_info: TypeInfo) -> Instance:
|
||||||
|
bases = []
|
||||||
|
for original_base in base_manager_info.bases:
|
||||||
|
if self.is_any_parametrized_manager(original_base):
|
||||||
|
if original_base.type is None:
|
||||||
|
raise helpers.IncompleteDefnException()
|
||||||
|
|
||||||
|
original_base = helpers.reparametrize_instance(original_base,
|
||||||
|
[Instance(self.model_classdef.info, [])])
|
||||||
|
bases.append(original_base)
|
||||||
|
|
||||||
|
new_manager_info = self.add_new_class_for_current_module(name, bases)
|
||||||
|
# copy fields to a new manager
|
||||||
|
new_cls_def_context = ClassDefContext(cls=new_manager_info.defn,
|
||||||
|
reason=self.ctx.reason,
|
||||||
|
api=self.api)
|
||||||
|
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
|
||||||
|
if isinstance(sym.node, FuncDef):
|
||||||
|
helpers.copy_method_to_another_class(new_cls_def_context,
|
||||||
|
self_type=custom_manager_type,
|
||||||
|
new_method_name=name,
|
||||||
|
method_node=sym.node)
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_sym = sym.copy()
|
||||||
|
if isinstance(new_sym.node, Var):
|
||||||
|
new_var = Var(name, type=sym.type)
|
||||||
|
new_var.info = new_manager_info
|
||||||
|
new_var._fullname = new_manager_info.fullname + '.' + name
|
||||||
|
new_sym.node = new_var
|
||||||
|
new_manager_info.names[name] = new_sym
|
||||||
|
|
||||||
|
return custom_manager_type
|
||||||
|
|
||||||
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
||||||
for manager_name, manager in model_cls._meta.managers_map.items():
|
for manager_name, manager in model_cls._meta.managers_map.items():
|
||||||
if self.model_class_has_attribute_defined(manager_name, traverse_mro=False):
|
manager_class_name = manager.__class__.__name__
|
||||||
sym = self.model_classdef.info.names.get(manager_name)
|
manager_fullname = helpers.get_class_fullname(manager.__class__)
|
||||||
assert sym is not None
|
try:
|
||||||
|
manager_info = self.lookup_typeinfo_or_incomplete_defn_error(manager_fullname)
|
||||||
if (sym.type is not None
|
except helpers.IncompleteDefnException as exc:
|
||||||
and isinstance(sym.type, Instance)
|
if not self.api.final_iteration:
|
||||||
and sym.type.type.has_base(fullnames.BASE_MANAGER_CLASS_FULLNAME)):
|
raise exc
|
||||||
# already defined and parametrized properly
|
else:
|
||||||
|
base_manager_fullname = helpers.get_class_fullname(manager.__class__.__bases__[0])
|
||||||
|
generated_managers = self.get_generated_manager_mappings(base_manager_fullname)
|
||||||
|
if manager_fullname not in generated_managers:
|
||||||
|
# not a generated manager, continue with the loop
|
||||||
continue
|
continue
|
||||||
|
real_manager_fullname = generated_managers[manager_fullname]
|
||||||
if getattr(manager, '_built_with_as_manager', False):
|
manager_info = self.lookup_typeinfo(real_manager_fullname) # type: ignore
|
||||||
# as_manager is not supported yet
|
if manager_info is None:
|
||||||
if not self.model_class_has_attribute_defined(manager_name, traverse_mro=True):
|
|
||||||
self.add_new_node_to_model_class(manager_name, AnyType(TypeOfAny.explicit))
|
|
||||||
continue
|
continue
|
||||||
|
manager_class_name = real_manager_fullname.rsplit('.', maxsplit=1)[1]
|
||||||
manager_fullname = self.resolve_manager_fullname(helpers.get_class_fullname(manager.__class__))
|
|
||||||
manager_info = self.lookup_typeinfo_or_exception(manager_fullname)
|
|
||||||
|
|
||||||
if manager_name not in self.model_classdef.info.names:
|
if manager_name not in self.model_classdef.info.names:
|
||||||
# manager not yet defined, just add models.Manager[ModelName]
|
|
||||||
manager_type = Instance(manager_info, [Instance(self.model_classdef.info, [])])
|
manager_type = Instance(manager_info, [Instance(self.model_classdef.info, [])])
|
||||||
self.add_new_node_to_model_class(manager_name, manager_type)
|
self.add_new_node_to_model_class(manager_name, manager_type)
|
||||||
|
else:
|
||||||
|
# creates new MODELNAME_MANAGERCLASSNAME class that represents manager parametrized with current model
|
||||||
|
if not self.has_any_parametrized_manager_as_base(manager_info):
|
||||||
|
continue
|
||||||
|
|
||||||
|
custom_model_manager_name = manager.model.__name__ + '_' + manager_class_name
|
||||||
|
try:
|
||||||
|
custom_manager_type = self.create_new_model_parametrized_manager(custom_model_manager_name,
|
||||||
|
base_manager_info=manager_info)
|
||||||
|
except helpers.IncompleteDefnException:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.add_new_node_to_model_class(manager_name, custom_manager_type)
|
||||||
|
|
||||||
|
|
||||||
class AddDefaultManagerAttribute(ModelClassInitializer):
|
class AddDefaultManagerAttribute(ModelClassInitializer):
|
||||||
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
||||||
if self.model_class_has_attribute_defined('_default_manager', traverse_mro=False):
|
# add _default_manager
|
||||||
return
|
if '_default_manager' not in self.model_classdef.info.names:
|
||||||
if model_cls._meta.default_manager is None:
|
|
||||||
return
|
|
||||||
if getattr(model_cls._meta.default_manager, '_built_with_as_manager', False):
|
|
||||||
self.add_new_node_to_model_class('_default_manager',
|
|
||||||
AnyType(TypeOfAny.explicit))
|
|
||||||
return
|
|
||||||
|
|
||||||
default_manager_fullname = helpers.get_class_fullname(model_cls._meta.default_manager.__class__)
|
default_manager_fullname = helpers.get_class_fullname(model_cls._meta.default_manager.__class__)
|
||||||
resolved_default_manager_fullname = self.resolve_manager_fullname(default_manager_fullname)
|
default_manager_info = self.lookup_typeinfo_or_incomplete_defn_error(default_manager_fullname)
|
||||||
|
|
||||||
default_manager_info = self.lookup_typeinfo_or_exception(resolved_default_manager_fullname)
|
|
||||||
default_manager = Instance(default_manager_info, [Instance(self.model_classdef.info, [])])
|
default_manager = Instance(default_manager_info, [Instance(self.model_classdef.info, [])])
|
||||||
self.add_new_node_to_model_class('_default_manager', default_manager)
|
self.add_new_node_to_model_class('_default_manager', default_manager)
|
||||||
|
|
||||||
@@ -251,38 +249,33 @@ class AddRelatedManagers(ModelClassInitializer):
|
|||||||
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
||||||
# add related managers
|
# add related managers
|
||||||
for relation in self.django_context.get_model_relations(model_cls):
|
for relation in self.django_context.get_model_relations(model_cls):
|
||||||
related_manager_attr_name = relation.get_accessor_name()
|
attname = relation.get_accessor_name()
|
||||||
if related_manager_attr_name is None:
|
if attname is None:
|
||||||
# no reverse accessor
|
# no reverse accessor
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self.model_class_has_attribute_defined(related_manager_attr_name, traverse_mro=False):
|
|
||||||
continue
|
|
||||||
|
|
||||||
related_model_cls = self.django_context.get_field_related_model_cls(relation)
|
related_model_cls = self.django_context.get_field_related_model_cls(relation)
|
||||||
if related_model_cls is None:
|
if related_model_cls is None:
|
||||||
# could not find a referenced model (maybe invalid to= value)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
related_model_info = self.lookup_class_typeinfo_or_exception(related_model_cls)
|
related_model_info = self.lookup_class_typeinfo_or_incomplete_defn_error(related_model_cls)
|
||||||
except sem_helpers.IncompleteDefnError as exc:
|
except helpers.IncompleteDefnException as exc:
|
||||||
if not self.api.final_iteration:
|
if not self.api.final_iteration:
|
||||||
raise exc
|
raise exc
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(relation, OneToOneRel):
|
if isinstance(relation, OneToOneRel):
|
||||||
self.add_new_node_to_model_class(related_manager_attr_name, Instance(related_model_info, []))
|
self.add_new_node_to_model_class(attname, Instance(related_model_info, []))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(relation, (ManyToOneRel, ManyToManyRel)):
|
if isinstance(relation, (ManyToOneRel, ManyToManyRel)):
|
||||||
try:
|
try:
|
||||||
related_manager_info = self.lookup_typeinfo_or_exception(
|
related_manager_info = self.lookup_typeinfo_or_incomplete_defn_error(fullnames.RELATED_MANAGER_CLASS) # noqa: E501
|
||||||
fullnames.RELATED_MANAGER_CLASS) # noqa: E501
|
|
||||||
if 'objects' not in related_model_info.names:
|
if 'objects' not in related_model_info.names:
|
||||||
raise sem_helpers.IncompleteDefnError()
|
raise helpers.IncompleteDefnException()
|
||||||
except sem_helpers.IncompleteDefnError as exc:
|
except helpers.IncompleteDefnException as exc:
|
||||||
if not self.api.final_iteration:
|
if not self.api.final_iteration:
|
||||||
raise exc
|
raise exc
|
||||||
else:
|
else:
|
||||||
@@ -295,15 +288,14 @@ class AddRelatedManagers(ModelClassInitializer):
|
|||||||
if (default_manager_type is None
|
if (default_manager_type is None
|
||||||
or not isinstance(default_manager_type, Instance)
|
or not isinstance(default_manager_type, Instance)
|
||||||
or default_manager_type.type.fullname == fullnames.MANAGER_CLASS_FULLNAME):
|
or default_manager_type.type.fullname == fullnames.MANAGER_CLASS_FULLNAME):
|
||||||
self.add_new_node_to_model_class(related_manager_attr_name, parametrized_related_manager_type)
|
self.add_new_node_to_model_class(attname, parametrized_related_manager_type)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
name = related_model_cls.__name__ + '_' + 'RelatedManager'
|
name = related_model_cls.__name__ + '_' + 'RelatedManager'
|
||||||
bases = [parametrized_related_manager_type, default_manager_type]
|
bases = [parametrized_related_manager_type, default_manager_type]
|
||||||
new_related_manager_info = self.add_new_class_for_current_module(name, bases,
|
new_related_manager_info = self.add_new_class_for_current_module(name, bases)
|
||||||
force_replace_existing=True)
|
|
||||||
self.add_new_node_to_model_class(related_manager_attr_name,
|
self.add_new_node_to_model_class(attname, Instance(new_related_manager_info, []))
|
||||||
Instance(new_related_manager_info, []))
|
|
||||||
|
|
||||||
|
|
||||||
class AddExtraFieldMethods(ModelClassInitializer):
|
class AddExtraFieldMethods(ModelClassInitializer):
|
||||||
@@ -311,7 +303,7 @@ class AddExtraFieldMethods(ModelClassInitializer):
|
|||||||
# get_FOO_display for choices
|
# get_FOO_display for choices
|
||||||
for field in self.django_context.get_model_fields(model_cls):
|
for field in self.django_context.get_model_fields(model_cls):
|
||||||
if field.choices:
|
if field.choices:
|
||||||
info = self.lookup_typeinfo_or_exception('builtins.str')
|
info = self.lookup_typeinfo_or_incomplete_defn_error('builtins.str')
|
||||||
return_type = Instance(info, [])
|
return_type = Instance(info, [])
|
||||||
common.add_method(self.ctx,
|
common.add_method(self.ctx,
|
||||||
name='get_{}_display'.format(field.attname),
|
name='get_{}_display'.format(field.attname),
|
||||||
@@ -341,7 +333,7 @@ class AddExtraFieldMethods(ModelClassInitializer):
|
|||||||
class AddMetaOptionsAttribute(ModelClassInitializer):
|
class AddMetaOptionsAttribute(ModelClassInitializer):
|
||||||
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
|
||||||
if '_meta' not in self.model_classdef.info.names:
|
if '_meta' not in self.model_classdef.info.names:
|
||||||
options_info = self.lookup_typeinfo_or_exception(fullnames.OPTIONS_CLASS_FULLNAME)
|
options_info = self.lookup_typeinfo_or_incomplete_defn_error(fullnames.OPTIONS_CLASS_FULLNAME)
|
||||||
self.add_new_node_to_model_class('_meta',
|
self.add_new_node_to_model_class('_meta',
|
||||||
Instance(options_info, [
|
Instance(options_info, [
|
||||||
Instance(self.model_classdef.info, [])
|
Instance(self.model_classdef.info, [])
|
||||||
@@ -363,8 +355,6 @@ def process_model_class(ctx: ClassDefContext,
|
|||||||
for initializer_cls in initializers:
|
for initializer_cls in initializers:
|
||||||
try:
|
try:
|
||||||
initializer_cls(ctx, django_context).run()
|
initializer_cls(ctx, django_context).run()
|
||||||
except sem_helpers.IncompleteDefnError as exc:
|
except helpers.IncompleteDefnException:
|
||||||
if not ctx.api.final_iteration:
|
if not ctx.api.final_iteration:
|
||||||
ctx.api.defer()
|
ctx.api.defer()
|
||||||
continue
|
|
||||||
raise exc
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from mypy.types import Type as MypyType
|
|||||||
from mypy.types import TypeOfAny
|
from mypy.types import TypeOfAny
|
||||||
|
|
||||||
from mypy_django_plugin.django.context import DjangoContext
|
from mypy_django_plugin.django.context import DjangoContext
|
||||||
from mypy_django_plugin.lib import chk_helpers, fullnames, helpers
|
from mypy_django_plugin.lib import fullnames, helpers
|
||||||
|
|
||||||
|
|
||||||
def typecheck_queryset_filter(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
|
def typecheck_queryset_filter(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
|
||||||
@@ -35,7 +35,7 @@ def typecheck_queryset_filter(ctx: MethodContext, django_context: DjangoContext)
|
|||||||
fullnames.QUERYSET_CLASS_FULLNAME))):
|
fullnames.QUERYSET_CLASS_FULLNAME))):
|
||||||
return ctx.default_return_type
|
return ctx.default_return_type
|
||||||
|
|
||||||
chk_helpers.check_types_compatible(ctx,
|
helpers.check_types_compatible(ctx,
|
||||||
expected_type=lookup_type,
|
expected_type=lookup_type,
|
||||||
actual_type=provided_type,
|
actual_type=provided_type,
|
||||||
error_message=f'Incompatible type for lookup {lookup_kwarg!r}:')
|
error_message=f'Incompatible type for lookup {lookup_kwarg!r}:')
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from mypy.types import TypeOfAny
|
|||||||
from mypy_django_plugin.django.context import (
|
from mypy_django_plugin.django.context import (
|
||||||
DjangoContext, LookupsAreUnsupported,
|
DjangoContext, LookupsAreUnsupported,
|
||||||
)
|
)
|
||||||
from mypy_django_plugin.lib import chk_helpers, fullnames, helpers
|
from mypy_django_plugin.lib import fullnames, helpers
|
||||||
|
|
||||||
|
|
||||||
def _extract_model_type_from_queryset(queryset_type: Instance) -> Optional[Instance]:
|
def _extract_model_type_from_queryset(queryset_type: Instance) -> Optional[Instance]:
|
||||||
@@ -30,7 +30,7 @@ def determine_proper_manager_type(ctx: FunctionContext) -> MypyType:
|
|||||||
default_return_type = ctx.default_return_type
|
default_return_type = ctx.default_return_type
|
||||||
assert isinstance(default_return_type, Instance)
|
assert isinstance(default_return_type, Instance)
|
||||||
|
|
||||||
outer_model_info = chk_helpers.get_typechecker_api(ctx).scope.active_class()
|
outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
|
||||||
if (outer_model_info is None
|
if (outer_model_info is None
|
||||||
or not outer_model_info.has_base(fullnames.MODEL_CLASS_FULLNAME)):
|
or not outer_model_info.has_base(fullnames.MODEL_CLASS_FULLNAME)):
|
||||||
return default_return_type
|
return default_return_type
|
||||||
@@ -55,7 +55,7 @@ def get_field_type_from_lookup(ctx: MethodContext, django_context: DjangoContext
|
|||||||
return AnyType(TypeOfAny.from_error)
|
return AnyType(TypeOfAny.from_error)
|
||||||
lookup_field = django_context.get_primary_key_field(related_model_cls)
|
lookup_field = django_context.get_primary_key_field(related_model_cls)
|
||||||
|
|
||||||
field_get_type = django_context.get_field_get_type(chk_helpers.get_typechecker_api(ctx),
|
field_get_type = django_context.get_field_get_type(helpers.get_typechecker_api(ctx),
|
||||||
lookup_field, method=method)
|
lookup_field, method=method)
|
||||||
return field_get_type
|
return field_get_type
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext,
|
|||||||
if field_lookups is None:
|
if field_lookups is None:
|
||||||
return AnyType(TypeOfAny.from_error)
|
return AnyType(TypeOfAny.from_error)
|
||||||
|
|
||||||
typechecker_api = chk_helpers.get_typechecker_api(ctx)
|
typechecker_api = helpers.get_typechecker_api(ctx)
|
||||||
if len(field_lookups) == 0:
|
if len(field_lookups) == 0:
|
||||||
if flat:
|
if flat:
|
||||||
primary_key_field = django_context.get_primary_key_field(model_cls)
|
primary_key_field = django_context.get_primary_key_field(model_cls)
|
||||||
@@ -80,7 +80,7 @@ def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext,
|
|||||||
column_type = django_context.get_field_get_type(typechecker_api, field,
|
column_type = django_context.get_field_get_type(typechecker_api, field,
|
||||||
method='values_list')
|
method='values_list')
|
||||||
column_types[field.attname] = column_type
|
column_types[field.attname] = column_type
|
||||||
return chk_helpers.make_oneoff_named_tuple(typechecker_api, 'Row', column_types)
|
return helpers.make_oneoff_named_tuple(typechecker_api, 'Row', column_types)
|
||||||
else:
|
else:
|
||||||
# flat=False, named=False, all fields
|
# flat=False, named=False, all fields
|
||||||
field_lookups = []
|
field_lookups = []
|
||||||
@@ -103,9 +103,9 @@ def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext,
|
|||||||
assert len(column_types) == 1
|
assert len(column_types) == 1
|
||||||
row_type = next(iter(column_types.values()))
|
row_type = next(iter(column_types.values()))
|
||||||
elif named:
|
elif named:
|
||||||
row_type = chk_helpers.make_oneoff_named_tuple(typechecker_api, 'Row', column_types)
|
row_type = helpers.make_oneoff_named_tuple(typechecker_api, 'Row', column_types)
|
||||||
else:
|
else:
|
||||||
row_type = chk_helpers.make_tuple(typechecker_api, list(column_types.values()))
|
row_type = helpers.make_tuple(typechecker_api, list(column_types.values()))
|
||||||
|
|
||||||
return row_type
|
return row_type
|
||||||
|
|
||||||
@@ -123,13 +123,13 @@ def extract_proper_type_queryset_values_list(ctx: MethodContext, django_context:
|
|||||||
if model_cls is None:
|
if model_cls is None:
|
||||||
return ctx.default_return_type
|
return ctx.default_return_type
|
||||||
|
|
||||||
flat_expr = chk_helpers.get_call_argument_by_name(ctx, 'flat')
|
flat_expr = helpers.get_call_argument_by_name(ctx, 'flat')
|
||||||
if flat_expr is not None and isinstance(flat_expr, NameExpr):
|
if flat_expr is not None and isinstance(flat_expr, NameExpr):
|
||||||
flat = helpers.parse_bool(flat_expr)
|
flat = helpers.parse_bool(flat_expr)
|
||||||
else:
|
else:
|
||||||
flat = False
|
flat = False
|
||||||
|
|
||||||
named_expr = chk_helpers.get_call_argument_by_name(ctx, 'named')
|
named_expr = helpers.get_call_argument_by_name(ctx, 'named')
|
||||||
if named_expr is not None and isinstance(named_expr, NameExpr):
|
if named_expr is not None and isinstance(named_expr, NameExpr):
|
||||||
named = helpers.parse_bool(named_expr)
|
named = helpers.parse_bool(named_expr)
|
||||||
else:
|
else:
|
||||||
@@ -188,5 +188,5 @@ def extract_proper_type_queryset_values(ctx: MethodContext, django_context: Djan
|
|||||||
|
|
||||||
column_types[field_lookup] = field_lookup_type
|
column_types[field_lookup] = field_lookup_type
|
||||||
|
|
||||||
row_type = chk_helpers.make_oneoff_typeddict(ctx.api, column_types, set(column_types.keys()))
|
row_type = helpers.make_typeddict(ctx.api, column_types, set(column_types.keys()))
|
||||||
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, row_type])
|
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, row_type])
|
||||||
|
|||||||
@@ -1,16 +1,26 @@
|
|||||||
from mypy.plugin import AttributeContext
|
from mypy.plugin import AttributeContext
|
||||||
from mypy.types import Instance
|
from mypy.types import Instance
|
||||||
from mypy.types import Type as MypyType
|
from mypy.types import Type as MypyType
|
||||||
|
from mypy.types import UnionType
|
||||||
|
|
||||||
from mypy_django_plugin.django.context import DjangoContext
|
from mypy_django_plugin.django.context import DjangoContext
|
||||||
from mypy_django_plugin.lib import chk_helpers, helpers
|
from mypy_django_plugin.lib import helpers
|
||||||
|
|
||||||
|
|
||||||
def set_auth_user_model_as_type_for_request_user(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
|
def set_auth_user_model_as_type_for_request_user(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
|
||||||
auth_user_model = django_context.settings.AUTH_USER_MODEL
|
auth_user_model = django_context.settings.AUTH_USER_MODEL
|
||||||
model_cls = django_context.apps_registry.get_model(auth_user_model)
|
user_cls = django_context.apps_registry.get_model(auth_user_model)
|
||||||
model_info = helpers.lookup_class_typeinfo(chk_helpers.get_typechecker_api(ctx), model_cls)
|
user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), user_cls)
|
||||||
if model_info is None:
|
|
||||||
|
if user_info is None:
|
||||||
return ctx.default_attr_type
|
return ctx.default_attr_type
|
||||||
|
|
||||||
return Instance(model_info, [])
|
# Imported here because django isn't properly loaded yet when module is loaded
|
||||||
|
from django.contrib.auth.models import AnonymousUser
|
||||||
|
|
||||||
|
anonymous_user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), AnonymousUser)
|
||||||
|
if anonymous_user_info is None:
|
||||||
|
# This shouldn't be able to happen, as we managed to import the model above...
|
||||||
|
return Instance(user_info, [])
|
||||||
|
|
||||||
|
return UnionType([Instance(user_info, []), Instance(anonymous_user_info, [])])
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from mypy.types import Type as MypyType
|
|||||||
from mypy.types import TypeOfAny, TypeType
|
from mypy.types import TypeOfAny, TypeType
|
||||||
|
|
||||||
from mypy_django_plugin.django.context import DjangoContext
|
from mypy_django_plugin.django.context import DjangoContext
|
||||||
from mypy_django_plugin.lib import chk_helpers, helpers
|
from mypy_django_plugin.lib import helpers
|
||||||
|
|
||||||
|
|
||||||
def get_user_model_hook(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
|
def get_user_model_hook(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
|
||||||
@@ -13,7 +13,7 @@ def get_user_model_hook(ctx: FunctionContext, django_context: DjangoContext) ->
|
|||||||
model_cls = django_context.apps_registry.get_model(auth_user_model)
|
model_cls = django_context.apps_registry.get_model(auth_user_model)
|
||||||
model_cls_fullname = helpers.get_class_fullname(model_cls)
|
model_cls_fullname = helpers.get_class_fullname(model_cls)
|
||||||
|
|
||||||
model_info = helpers.lookup_fully_qualified_typeinfo(chk_helpers.get_typechecker_api(ctx),
|
model_info = helpers.lookup_fully_qualified_typeinfo(helpers.get_typechecker_api(ctx),
|
||||||
model_cls_fullname)
|
model_cls_fullname)
|
||||||
if model_info is None:
|
if model_info is None:
|
||||||
return AnyType(TypeOfAny.unannotated)
|
return AnyType(TypeOfAny.unannotated)
|
||||||
@@ -28,7 +28,7 @@ def get_type_of_settings_attribute(ctx: AttributeContext, django_context: Django
|
|||||||
ctx.api.fail(f"'Settings' object has no attribute {setting_name!r}", ctx.context)
|
ctx.api.fail(f"'Settings' object has no attribute {setting_name!r}", ctx.context)
|
||||||
return ctx.default_attr_type
|
return ctx.default_attr_type
|
||||||
|
|
||||||
typechecker_api = chk_helpers.get_typechecker_api(ctx)
|
typechecker_api = helpers.get_typechecker_api(ctx)
|
||||||
|
|
||||||
# first look for the setting in the project settings file, then global settings
|
# first look for the setting in the project settings file, then global settings
|
||||||
settings_module = typechecker_api.modules.get(django_context.django_settings_module)
|
settings_module = typechecker_api.modules.get(django_context.django_settings_module)
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from mypy.checker import gen_unique_name
|
|
||||||
from mypy.nodes import NameExpr, TypeInfo, SymbolTableNode, StrExpr
|
|
||||||
from mypy.types import Type as MypyType, TypeVarType, TypeVarDef, Instance
|
|
||||||
|
|
||||||
from mypy_django_plugin.lib import helpers, fullnames
|
|
||||||
from mypy_django_plugin.transformers.managers import iter_all_custom_queryset_methods
|
|
||||||
|
|
||||||
|
|
||||||
class CreateNewManagerClassFrom_FromQuerySet(helpers.DynamicClassPluginCallback):
|
|
||||||
def set_manager_mapping(self, runtime_manager_fullname: str, generated_manager_fullname: str) -> None:
|
|
||||||
base_model_info = self.lookup_typeinfo_or_defer(fullnames.MODEL_CLASS_FULLNAME)
|
|
||||||
assert base_model_info is not None
|
|
||||||
managers_metadata = base_model_info.metadata.setdefault('managers', {})
|
|
||||||
managers_metadata[runtime_manager_fullname] = generated_manager_fullname
|
|
||||||
|
|
||||||
def create_typevar_in_current_module(self, name: str,
|
|
||||||
upper_bound: Optional[MypyType] = None) -> TypeVarDef:
|
|
||||||
tvar_name = gen_unique_name(name, self.semanal_api.globals)
|
|
||||||
tvar_def = TypeVarDef(tvar_name,
|
|
||||||
fullname=self.semanal_api.cur_mod_id + '.' + tvar_name,
|
|
||||||
id=-1,
|
|
||||||
values=[],
|
|
||||||
upper_bound=upper_bound)
|
|
||||||
return tvar_def
|
|
||||||
|
|
||||||
def create_new_dynamic_class(self) -> None:
|
|
||||||
# extract Manager class which will act as base
|
|
||||||
callee = self.get_callee()
|
|
||||||
fullname = callee.fullname or callee.expr.fullname
|
|
||||||
callee_manager_info = self.lookup_typeinfo_or_defer(fullname)
|
|
||||||
if callee_manager_info is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# extract queryset from which we're going to copy methods
|
|
||||||
passed_queryset_name_expr = self.call_expr.args[0]
|
|
||||||
assert isinstance(passed_queryset_name_expr, NameExpr)
|
|
||||||
queryset_class_name = passed_queryset_name_expr.name
|
|
||||||
sym = self.lookup_same_module_or_defer(queryset_class_name)
|
|
||||||
if sym is None:
|
|
||||||
return None
|
|
||||||
assert isinstance(sym.node, TypeInfo)
|
|
||||||
passed_queryset_info = sym.node
|
|
||||||
|
|
||||||
# for TypeVar bound
|
|
||||||
base_model_info = self.lookup_typeinfo_or_defer(fullnames.MODEL_CLASS_FULLNAME)
|
|
||||||
if base_model_info is None:
|
|
||||||
return
|
|
||||||
model_tvar_defn = self.create_typevar_in_current_module('_M', upper_bound=Instance(base_model_info, []))
|
|
||||||
model_tvar_type = TypeVarType(model_tvar_defn)
|
|
||||||
|
|
||||||
# make Manager[_T]
|
|
||||||
parent_manager_type = Instance(callee_manager_info, [model_tvar_type])
|
|
||||||
|
|
||||||
# instantiate with a proper model, Manager[MyModel], filling all Manager type vars in process
|
|
||||||
queryset_type = Instance(passed_queryset_info, [Instance(base_model_info, [])])
|
|
||||||
new_manager_info = self.new_typeinfo(self.class_name,
|
|
||||||
bases=[queryset_type, parent_manager_type])
|
|
||||||
new_manager_info.defn.type_vars = [model_tvar_defn]
|
|
||||||
new_manager_info.type_vars = [model_tvar_defn.name]
|
|
||||||
new_manager_info.set_line(self.call_expr)
|
|
||||||
|
|
||||||
# copy methods from passed_queryset_info with self type replaced
|
|
||||||
# self_type = Instance(new_manager_info, [model_tvar_type])
|
|
||||||
# for name, method_node in iter_all_custom_queryset_methods(passed_queryset_info):
|
|
||||||
# self.add_method_from_signature(method_node,
|
|
||||||
# name,
|
|
||||||
# self_type,
|
|
||||||
# new_manager_info.defn)
|
|
||||||
|
|
||||||
new_manager_sym = SymbolTableNode(self.semanal_api.current_symbol_kind(),
|
|
||||||
new_manager_info,
|
|
||||||
plugin_generated=True)
|
|
||||||
self.semanal_api.add_symbol_table_node(self.class_name, new_manager_sym)
|
|
||||||
|
|
||||||
# add mapping between generated manager and current one
|
|
||||||
runtime_manager_class_name = None
|
|
||||||
if 'class_name' in self.call_expr.arg_names:
|
|
||||||
class_name_arg = self.call_expr.args[self.call_expr.arg_names.index('class_name')]
|
|
||||||
if isinstance(class_name_arg, StrExpr):
|
|
||||||
runtime_manager_class_name = class_name_arg.value
|
|
||||||
|
|
||||||
new_manager_name = runtime_manager_class_name or (callee_manager_info.name + 'From' + queryset_class_name)
|
|
||||||
self.set_manager_mapping(f'django.db.models.manager.{new_manager_name}',
|
|
||||||
new_manager_info.fullname)
|
|
||||||
@@ -1,303 +0,0 @@
|
|||||||
from abc import abstractmethod
|
|
||||||
from typing import Type, Optional
|
|
||||||
|
|
||||||
from django.db.models.base import Model
|
|
||||||
from django.db.models.fields.related import OneToOneField, ForeignKey
|
|
||||||
from mypy.nodes import TypeInfo, Var, SymbolTableNode, MDEF, Argument, ARG_STAR2
|
|
||||||
from mypy.plugin import ClassDefContext
|
|
||||||
from mypy.plugins import common
|
|
||||||
from mypy.semanal import dummy_context
|
|
||||||
from mypy.types import Instance, TypeOfAny, AnyType
|
|
||||||
from mypy.types import Type as MypyType
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.db.models.fields import DateField, DateTimeField
|
|
||||||
from mypy_django_plugin.lib import helpers, fullnames, sem_helpers
|
|
||||||
from mypy_django_plugin.transformers import fields
|
|
||||||
from mypy_django_plugin.transformers.fields import get_field_type
|
|
||||||
from mypy_django_plugin.transformers2 import new_helpers
|
|
||||||
|
|
||||||
|
|
||||||
class TransformModelClassCallback(helpers.ClassDefPluginCallback):
|
|
||||||
def get_real_manager_fullname(self, manager_fullname: str) -> str:
|
|
||||||
model_info = self.lookup_typeinfo_or_defer(fullnames.MODEL_CLASS_FULLNAME)
|
|
||||||
real_manager_fullname = model_info.metadata.get('managers', {}).get(manager_fullname, manager_fullname)
|
|
||||||
return real_manager_fullname
|
|
||||||
|
|
||||||
def modify_class_defn(self) -> None:
|
|
||||||
model_cls = self.django_context.get_model_class_by_fullname(self.class_defn.fullname)
|
|
||||||
if model_cls is None:
|
|
||||||
return None
|
|
||||||
return self.modify_model_class_defn(model_cls)
|
|
||||||
|
|
||||||
def add_new_model_attribute(self, name: str, typ: MypyType, force_replace: bool = False) -> None:
|
|
||||||
model_info = self.class_defn.info
|
|
||||||
if name in model_info.names and not force_replace:
|
|
||||||
raise ValueError('Attribute already exists on the model')
|
|
||||||
|
|
||||||
var = Var(name, type=typ)
|
|
||||||
var.info = model_info
|
|
||||||
var._fullname = self.semanal_api.qualified_name(name)
|
|
||||||
var.is_initialized_in_class = True
|
|
||||||
|
|
||||||
sym = SymbolTableNode(MDEF, var, plugin_generated=True)
|
|
||||||
error_context = None if force_replace else dummy_context()
|
|
||||||
added = self.semanal_api.add_symbol_table_node(name, sym, context=error_context)
|
|
||||||
assert added
|
|
||||||
|
|
||||||
def lookup_typeinfo_for_class_or_defer(self, klass: type, *,
|
|
||||||
reason_for_defer: Optional[str] = None) -> Optional[TypeInfo]:
|
|
||||||
manager_cls_fullname = helpers.get_class_fullname(klass)
|
|
||||||
return self.lookup_typeinfo_or_defer(manager_cls_fullname,
|
|
||||||
reason_for_defer=reason_for_defer)
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class AddDefaultManagerCallback(TransformModelClassCallback):
|
|
||||||
def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None:
|
|
||||||
if ('_default_manager' in self.class_defn.info.names
|
|
||||||
or runtime_model_cls._meta.default_manager is None):
|
|
||||||
return None
|
|
||||||
|
|
||||||
runtime_default_manager_class = runtime_model_cls._meta.default_manager.__class__
|
|
||||||
runtime_manager_cls_fullname = new_helpers.get_class_fullname(runtime_default_manager_class)
|
|
||||||
manager_cls_fullname = self.get_real_manager_fullname(runtime_manager_cls_fullname)
|
|
||||||
|
|
||||||
default_manager_info = self.lookup_typeinfo_or_defer(manager_cls_fullname)
|
|
||||||
if default_manager_info is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.add_new_model_attribute('_default_manager',
|
|
||||||
Instance(default_manager_info, [Instance(self.class_defn.info, [])]))
|
|
||||||
|
|
||||||
|
|
||||||
class AddManagersCallback(TransformModelClassCallback):
|
|
||||||
def modify_model_class_defn(self, runtime_model_cls: Type[models.Model]) -> None:
|
|
||||||
for manager_name, manager in runtime_model_cls._meta.managers_map.items():
|
|
||||||
if manager_name in self.class_defn.info.names:
|
|
||||||
# already defined on the current model class, in file or at a previous iteration
|
|
||||||
continue
|
|
||||||
|
|
||||||
manager_info = self.lookup_typeinfo_for_class_or_defer(manager.__class__)
|
|
||||||
if manager_info is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
manager_type = Instance(manager_info, [Instance(self.class_defn.info, [])])
|
|
||||||
self.add_new_model_attribute(manager_name, manager_type)
|
|
||||||
|
|
||||||
|
|
||||||
class AddPrimaryKeyIfDoesNotExist(TransformModelClassCallback):
|
|
||||||
"""
|
|
||||||
Adds default primary key to models which does not define their own.
|
|
||||||
class User(models.Model):
|
|
||||||
name = models.TextField()
|
|
||||||
"""
|
|
||||||
|
|
||||||
def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None:
|
|
||||||
auto_pk_field = runtime_model_cls._meta.auto_field
|
|
||||||
if auto_pk_field is None:
|
|
||||||
# defined explicitly
|
|
||||||
return None
|
|
||||||
auto_pk_field_name = auto_pk_field.attname
|
|
||||||
if auto_pk_field_name in self.class_defn.info.names:
|
|
||||||
# added on previous iteration
|
|
||||||
return None
|
|
||||||
|
|
||||||
auto_pk_field_info = self.lookup_typeinfo_for_class_or_defer(auto_pk_field.__class__)
|
|
||||||
if auto_pk_field_info is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
self.add_new_model_attribute(auto_pk_field_name,
|
|
||||||
fields.get_field_type(auto_pk_field_info, is_nullable=False))
|
|
||||||
|
|
||||||
|
|
||||||
class AddRelatedManagersCallback(TransformModelClassCallback):
|
|
||||||
def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None:
|
|
||||||
for reverse_manager_name, relation in self.django_context.get_model_relations(runtime_model_cls):
|
|
||||||
if (reverse_manager_name is None
|
|
||||||
or reverse_manager_name in self.class_defn.info.names):
|
|
||||||
continue
|
|
||||||
|
|
||||||
self.add_new_model_attribute(reverse_manager_name, AnyType(TypeOfAny.implementation_artifact))
|
|
||||||
#
|
|
||||||
# related_model_cls = self.django_context.get_field_related_model_cls(relation)
|
|
||||||
# if related_model_cls is None:
|
|
||||||
# # could not find a referenced model (maybe invalid to= value, or GenericForeignKey)
|
|
||||||
# continue
|
|
||||||
#
|
|
||||||
# related_model_info = self.lookup_typeinfo_for_class_or_defer(related_model_cls)
|
|
||||||
# if related_model_info is None:
|
|
||||||
# continue
|
|
||||||
#
|
|
||||||
# if isinstance(relation, OneToOneRel):
|
|
||||||
# self.add_new_model_attribute(reverse_manager_name,
|
|
||||||
# Instance(related_model_info, []))
|
|
||||||
# elif isinstance(relation, (ManyToOneRel, ManyToManyRel)):
|
|
||||||
# related_manager_info = self.lookup_typeinfo_or_defer(fullnames.RELATED_MANAGER_CLASS)
|
|
||||||
# if related_manager_info is None:
|
|
||||||
# if not self.defer_till_next_iteration(self.class_defn,
|
|
||||||
# reason=f'{fullnames.RELATED_MANAGER_CLASS!r} is not available for lookup'):
|
|
||||||
# raise TypeInfoNotFound(fullnames.RELATED_MANAGER_CLASS)
|
|
||||||
# continue
|
|
||||||
#
|
|
||||||
# # get type of default_manager for model
|
|
||||||
# default_manager_fullname = helpers.get_class_fullname(related_model_cls._meta.default_manager.__class__)
|
|
||||||
# reason_for_defer = (f'Trying to lookup default_manager {default_manager_fullname!r} '
|
|
||||||
# f'of model {helpers.get_class_fullname(related_model_cls)!r}')
|
|
||||||
# default_manager_info = self.lookup_typeinfo_or_defer(default_manager_fullname,
|
|
||||||
# reason_for_defer=reason_for_defer)
|
|
||||||
# if default_manager_info is None:
|
|
||||||
# continue
|
|
||||||
#
|
|
||||||
# default_manager_type = Instance(default_manager_info, [Instance(related_model_info, [])])
|
|
||||||
#
|
|
||||||
# # related_model_cls._meta.default_manager.__class__
|
|
||||||
# # # we're making a subclass of 'objects', need to have it defined
|
|
||||||
# # if 'objects' not in related_model_info.names:
|
|
||||||
# # if not self.defer_till_next_iteration(self.class_defn,
|
|
||||||
# # reason=f"'objects' manager is not yet defined on {related_model_info.fullname!r}"):
|
|
||||||
# # raise AttributeNotFound(self.class_defn.info, 'objects')
|
|
||||||
# # continue
|
|
||||||
#
|
|
||||||
# related_manager_type = Instance(related_manager_info,
|
|
||||||
# [Instance(related_model_info, [])])
|
|
||||||
# #
|
|
||||||
# # objects_sym = related_model_info.names['objects']
|
|
||||||
# # default_manager_type = objects_sym.type
|
|
||||||
# # if default_manager_type is None:
|
|
||||||
# # # dynamic base class, extract from django_context
|
|
||||||
# # default_manager_cls = related_model_cls._meta.default_manager.__class__
|
|
||||||
# # default_manager_info = self.lookup_typeinfo_for_class_or_defer(default_manager_cls)
|
|
||||||
# # if default_manager_info is None:
|
|
||||||
# # continue
|
|
||||||
# # default_manager_type = Instance(default_manager_info, [Instance(related_model_info, [])])
|
|
||||||
#
|
|
||||||
# if (not isinstance(default_manager_type, Instance)
|
|
||||||
# or default_manager_type.type.fullname == fullnames.MANAGER_CLASS_FULLNAME):
|
|
||||||
# # if not defined or trivial -> just return RelatedManager[Model]
|
|
||||||
# self.add_new_model_attribute(reverse_manager_name, related_manager_type)
|
|
||||||
# continue
|
|
||||||
#
|
|
||||||
# # make anonymous class
|
|
||||||
# name = gen_unique_name(related_model_cls.__name__ + '_' + 'RelatedManager',
|
|
||||||
# self.semanal_api.current_symbol_table())
|
|
||||||
# bases = [related_manager_type, default_manager_type]
|
|
||||||
# new_manager_info = self.new_typeinfo(name, bases)
|
|
||||||
# self.add_new_model_attribute(reverse_manager_name, Instance(new_manager_info, []))
|
|
||||||
|
|
||||||
|
|
||||||
class AddForeignPrimaryKeys(TransformModelClassCallback):
|
|
||||||
def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None:
|
|
||||||
for field in runtime_model_cls._meta.get_fields():
|
|
||||||
if not isinstance(field, (OneToOneField, ForeignKey)):
|
|
||||||
continue
|
|
||||||
rel_pk_field_name = field.attname
|
|
||||||
if rel_pk_field_name in self.class_defn.info.names:
|
|
||||||
continue
|
|
||||||
|
|
||||||
related_model_cls = self.django_context.get_field_related_model_cls(field)
|
|
||||||
if related_model_cls is None:
|
|
||||||
field_sym = self.class_defn.info.get(field.name)
|
|
||||||
if field_sym is not None and field_sym.node is not None:
|
|
||||||
error_context = field_sym.node
|
|
||||||
else:
|
|
||||||
error_context = self.class_defn
|
|
||||||
self.semanal_api.fail(f'Cannot find model {field.related_model!r} '
|
|
||||||
f'referenced in field {field.name!r} ',
|
|
||||||
ctx=error_context)
|
|
||||||
self.add_new_model_attribute(rel_pk_field_name, AnyType(TypeOfAny.from_error))
|
|
||||||
continue
|
|
||||||
if related_model_cls._meta.abstract:
|
|
||||||
continue
|
|
||||||
|
|
||||||
rel_pk_field = self.django_context.get_primary_key_field(related_model_cls)
|
|
||||||
rel_pk_field_info = self.lookup_typeinfo_for_class_or_defer(rel_pk_field.__class__)
|
|
||||||
if rel_pk_field_info is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
field_type = get_field_type(rel_pk_field_info,
|
|
||||||
is_nullable=self.django_context.get_field_nullability(field))
|
|
||||||
self.add_new_model_attribute(rel_pk_field_name, field_type)
|
|
||||||
|
|
||||||
|
|
||||||
class InjectAnyAsBaseForNestedMeta(TransformModelClassCallback):
|
|
||||||
"""
|
|
||||||
Replaces
|
|
||||||
class MyModel(models.Model):
|
|
||||||
class Meta:
|
|
||||||
pass
|
|
||||||
with
|
|
||||||
class MyModel(models.Model):
|
|
||||||
class Meta(Any):
|
|
||||||
pass
|
|
||||||
to get around incompatible Meta inner classes for different models.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def modify_class_defn(self) -> None:
|
|
||||||
meta_node = sem_helpers.get_nested_meta_node_for_current_class(self.class_defn.info)
|
|
||||||
if meta_node is None:
|
|
||||||
return None
|
|
||||||
meta_node.fallback_to_any = True
|
|
||||||
|
|
||||||
|
|
||||||
class AddMetaOptionsAttribute(TransformModelClassCallback):
|
|
||||||
def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None:
|
|
||||||
if '_meta' not in self.class_defn.info.names:
|
|
||||||
options_info = self.lookup_typeinfo_or_defer(fullnames.OPTIONS_CLASS_FULLNAME)
|
|
||||||
if options_info is not None:
|
|
||||||
self.add_new_model_attribute('_meta',
|
|
||||||
Instance(options_info, [
|
|
||||||
Instance(self.class_defn.info, [])
|
|
||||||
]))
|
|
||||||
|
|
||||||
|
|
||||||
class AddExtraFieldMethods(TransformModelClassCallback):
|
|
||||||
def modify_model_class_defn(self, runtime_model_cls: Type[Model]) -> None:
|
|
||||||
# get_FOO_display for choices
|
|
||||||
for field in self.django_context.get_model_fields(runtime_model_cls):
|
|
||||||
if field.choices:
|
|
||||||
info = self.lookup_typeinfo_or_defer('builtins.str')
|
|
||||||
return_type = Instance(info, [])
|
|
||||||
common.add_method(self.ctx,
|
|
||||||
name='get_{}_display'.format(field.attname),
|
|
||||||
args=[],
|
|
||||||
return_type=return_type)
|
|
||||||
|
|
||||||
# get_next_by, get_previous_by for Date, DateTime
|
|
||||||
for field in self.django_context.get_model_fields(runtime_model_cls):
|
|
||||||
if isinstance(field, (DateField, DateTimeField)) and not field.null:
|
|
||||||
return_type = Instance(self.class_defn.info, [])
|
|
||||||
common.add_method(self.ctx,
|
|
||||||
name='get_next_by_{}'.format(field.attname),
|
|
||||||
args=[Argument(Var('kwargs', AnyType(TypeOfAny.explicit)),
|
|
||||||
AnyType(TypeOfAny.explicit),
|
|
||||||
initializer=None,
|
|
||||||
kind=ARG_STAR2)],
|
|
||||||
return_type=return_type)
|
|
||||||
common.add_method(self.ctx,
|
|
||||||
name='get_previous_by_{}'.format(field.attname),
|
|
||||||
args=[Argument(Var('kwargs', AnyType(TypeOfAny.explicit)),
|
|
||||||
AnyType(TypeOfAny.explicit),
|
|
||||||
initializer=None,
|
|
||||||
kind=ARG_STAR2)],
|
|
||||||
return_type=return_type)
|
|
||||||
|
|
||||||
|
|
||||||
class ModelCallback(helpers.ClassDefPluginCallback):
|
|
||||||
def __call__(self, ctx: ClassDefContext) -> None:
|
|
||||||
callback_classes = [
|
|
||||||
AddManagersCallback,
|
|
||||||
AddPrimaryKeyIfDoesNotExist,
|
|
||||||
AddForeignPrimaryKeys,
|
|
||||||
AddDefaultManagerCallback,
|
|
||||||
AddRelatedManagersCallback,
|
|
||||||
InjectAnyAsBaseForNestedMeta,
|
|
||||||
AddMetaOptionsAttribute,
|
|
||||||
AddExtraFieldMethods,
|
|
||||||
]
|
|
||||||
for callback_cls in callback_classes:
|
|
||||||
callback = callback_cls(self.plugin)
|
|
||||||
callback.__call__(ctx)
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
from typing import Union
|
|
||||||
|
|
||||||
from mypy.nodes import TypeInfo, MypyFile
|
|
||||||
|
|
||||||
|
|
||||||
class IncompleteDefnError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class TypeInfoNotFound(IncompleteDefnError):
|
|
||||||
def __init__(self, fullname: str) -> None:
|
|
||||||
super().__init__(f'It is final iteration and required type {fullname!r} is not ready yet.')
|
|
||||||
|
|
||||||
|
|
||||||
class AttributeNotFound(IncompleteDefnError):
|
|
||||||
def __init__(self, node: Union[TypeInfo, MypyFile], attrname: str) -> None:
|
|
||||||
super().__init__(f'Attribute {attrname!r} is not defined for the {node.fullname!r}.')
|
|
||||||
|
|
||||||
|
|
||||||
class NameNotFound(IncompleteDefnError):
|
|
||||||
def __init__(self, name: str) -> None:
|
|
||||||
super().__init__(f'Could not find {name!r} in the current activated namespaces')
|
|
||||||
|
|
||||||
|
|
||||||
class SymbolAdditionNotPossible(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_class_fullname(klass: type) -> str:
|
|
||||||
return klass.__module__ + '.' + klass.__qualname__
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
from mypy.checker import gen_unique_name
|
|
||||||
from mypy.plugin import AttributeContext
|
|
||||||
from mypy.types import Instance
|
|
||||||
from mypy.types import Type as MypyType
|
|
||||||
|
|
||||||
from django.db.models.fields.reverse_related import ForeignObjectRel, OneToOneRel, ManyToOneRel, ManyToManyRel
|
|
||||||
|
|
||||||
from mypy_django_plugin.lib import helpers, fullnames
|
|
||||||
from mypy_django_plugin.lib.helpers import GetAttributeCallback
|
|
||||||
|
|
||||||
|
|
||||||
class GetRelatedManagerCallback(GetAttributeCallback):
|
|
||||||
obj_type: Instance
|
|
||||||
|
|
||||||
def get_related_manager_type(self, relation: ForeignObjectRel) -> MypyType:
|
|
||||||
related_model_cls = self.django_context.get_field_related_model_cls(relation)
|
|
||||||
if related_model_cls is None:
|
|
||||||
# could not find a referenced model (maybe invalid to= value, or GenericForeignKey)
|
|
||||||
# TODO: show error
|
|
||||||
return self.default_attr_type
|
|
||||||
|
|
||||||
related_model_info = self.lookup_typeinfo(helpers.get_class_fullname(related_model_cls))
|
|
||||||
if related_model_info is None:
|
|
||||||
# TODO: show error
|
|
||||||
return self.default_attr_type
|
|
||||||
|
|
||||||
if isinstance(relation, OneToOneRel):
|
|
||||||
return Instance(related_model_info, [])
|
|
||||||
|
|
||||||
elif isinstance(relation, (ManyToOneRel, ManyToManyRel)):
|
|
||||||
related_manager_info = self.lookup_typeinfo(fullnames.RELATED_MANAGER_CLASS)
|
|
||||||
if related_manager_info is None:
|
|
||||||
return self.default_attr_type
|
|
||||||
|
|
||||||
# get type of default_manager for model
|
|
||||||
default_manager_fullname = helpers.get_class_fullname(related_model_cls._meta.default_manager.__class__)
|
|
||||||
default_manager_info = self.lookup_typeinfo(default_manager_fullname)
|
|
||||||
if default_manager_info is None:
|
|
||||||
return self.default_attr_type
|
|
||||||
|
|
||||||
default_manager_type = Instance(default_manager_info, [Instance(related_model_info, [])])
|
|
||||||
related_manager_type = Instance(related_manager_info,
|
|
||||||
[Instance(related_model_info, [])])
|
|
||||||
|
|
||||||
if (not isinstance(default_manager_type, Instance)
|
|
||||||
or default_manager_type.type.fullname == fullnames.MANAGER_CLASS_FULLNAME):
|
|
||||||
# if not defined or trivial -> just return RelatedManager[Model]
|
|
||||||
return related_manager_type
|
|
||||||
|
|
||||||
# make anonymous class
|
|
||||||
name = gen_unique_name(related_model_cls.__name__ + '_' + 'RelatedManager',
|
|
||||||
self.obj_type.type.names)
|
|
||||||
bases = [related_manager_type, default_manager_type]
|
|
||||||
new_manager_info = self.new_typeinfo(name, bases)
|
|
||||||
return Instance(new_manager_info, [])
|
|
||||||
|
|
||||||
def __call__(self, ctx: AttributeContext):
|
|
||||||
super().__call__(ctx)
|
|
||||||
assert isinstance(self.obj_type, Instance)
|
|
||||||
|
|
||||||
model_fullname = self.obj_type.type.fullname
|
|
||||||
model_cls = self.django_context.get_model_class_by_fullname(model_fullname)
|
|
||||||
if model_cls is None:
|
|
||||||
return self.default_attr_type
|
|
||||||
for reverse_manager_name, relation in self.django_context.get_model_relations(model_cls):
|
|
||||||
if reverse_manager_name == self.name:
|
|
||||||
return self.get_related_manager_type(relation)
|
|
||||||
|
|
||||||
return self.default_attr_type
|
|
||||||
@@ -64,7 +64,12 @@ IGNORED_ERRORS = {
|
|||||||
'Incompatible types in string interpolation',
|
'Incompatible types in string interpolation',
|
||||||
'"None" has no attribute',
|
'"None" has no attribute',
|
||||||
'has no attribute "assert',
|
'has no attribute "assert',
|
||||||
'Unsupported dynamic base class'
|
'Unsupported dynamic base class',
|
||||||
|
'error: "HttpResponse" has no attribute "streaming_content"',
|
||||||
|
'error: "HttpResponse" has no attribute "context_data"',
|
||||||
|
],
|
||||||
|
'admin_inlines': [
|
||||||
|
'error: "HttpResponse" has no attribute "rendered_content"',
|
||||||
],
|
],
|
||||||
'admin_utils': [
|
'admin_utils': [
|
||||||
'"Article" has no attribute "non_field"',
|
'"Article" has no attribute "non_field"',
|
||||||
@@ -104,6 +109,7 @@ IGNORED_ERRORS = {
|
|||||||
],
|
],
|
||||||
'builtin_server': [
|
'builtin_server': [
|
||||||
'"ServerHandler" has no attribute',
|
'"ServerHandler" has no attribute',
|
||||||
|
'Incompatible types in assignment (expression has type "Tuple[BytesIO, BytesIO]"',
|
||||||
],
|
],
|
||||||
'bulk_create': [
|
'bulk_create': [
|
||||||
'has incompatible type "List[Country]"; expected "Iterable[TwoFields]"',
|
'has incompatible type "List[Country]"; expected "Iterable[TwoFields]"',
|
||||||
@@ -164,7 +170,8 @@ IGNORED_ERRORS = {
|
|||||||
'Incompatible types in assignment (expression has type "Optional[Any]", variable has type "FloatModel")'
|
'Incompatible types in assignment (expression has type "Optional[Any]", variable has type "FloatModel")'
|
||||||
],
|
],
|
||||||
'decorators': [
|
'decorators': [
|
||||||
'"Type[object]" has no attribute "method"'
|
'"Type[object]" has no attribute "method"',
|
||||||
|
'Value of type variable "_T" of function cannot be "descriptor_wrapper"'
|
||||||
],
|
],
|
||||||
'expressions_window': [
|
'expressions_window': [
|
||||||
'has incompatible type "str"'
|
'has incompatible type "str"'
|
||||||
@@ -178,6 +185,8 @@ IGNORED_ERRORS = {
|
|||||||
],
|
],
|
||||||
'files': [
|
'files': [
|
||||||
'Incompatible types in assignment (expression has type "IOBase", variable has type "File")',
|
'Incompatible types in assignment (expression has type "IOBase", variable has type "File")',
|
||||||
|
'Argument 1 to "TextIOWrapper" has incompatible type "File"; expected "BinaryIO"',
|
||||||
|
'Incompatible types in assignment (expression has type "BinaryIO", variable has type "File")',
|
||||||
],
|
],
|
||||||
'filtered_relation': [
|
'filtered_relation': [
|
||||||
'has no attribute "name"',
|
'has no attribute "name"',
|
||||||
@@ -191,7 +200,7 @@ IGNORED_ERRORS = {
|
|||||||
],
|
],
|
||||||
'from_db_value': [
|
'from_db_value': [
|
||||||
'"Cash" has no attribute',
|
'"Cash" has no attribute',
|
||||||
'Argument 1 to "__str__" of "Decimal"',
|
'"__str__" of "Decimal"',
|
||||||
],
|
],
|
||||||
'get_object_or_404': [
|
'get_object_or_404': [
|
||||||
'Argument 1 to "get_object_or_404" has incompatible type "str"; '
|
'Argument 1 to "get_object_or_404" has incompatible type "str"; '
|
||||||
@@ -240,6 +249,7 @@ IGNORED_ERRORS = {
|
|||||||
],
|
],
|
||||||
'middleware': [
|
'middleware': [
|
||||||
re.compile(r'"(HttpRequest|WSGIRequest)" has no attribute'),
|
re.compile(r'"(HttpRequest|WSGIRequest)" has no attribute'),
|
||||||
|
'Incompatible types in assignment (expression has type "HttpResponseBase", variable has type "HttpResponse")',
|
||||||
],
|
],
|
||||||
'many_to_many': [
|
'many_to_many': [
|
||||||
'(expression has type "List[Article]", variable has type "Article_RelatedManager2',
|
'(expression has type "List[Article]", variable has type "Article_RelatedManager2',
|
||||||
@@ -276,6 +286,7 @@ IGNORED_ERRORS = {
|
|||||||
'"ImageFile" has no attribute "was_opened"',
|
'"ImageFile" has no attribute "was_opened"',
|
||||||
'Incompatible type for "size" of "FloatModel" (got "object", expected "Union[float, int, str, Combinable]")',
|
'Incompatible type for "size" of "FloatModel" (got "object", expected "Union[float, int, str, Combinable]")',
|
||||||
'Incompatible type for "value" of "IntegerModel" (got "object", expected',
|
'Incompatible type for "value" of "IntegerModel" (got "object", expected',
|
||||||
|
'"Child" has no attribute "get_foo_display"',
|
||||||
],
|
],
|
||||||
'model_forms': [
|
'model_forms': [
|
||||||
'"render" of "Widget"',
|
'"render" of "Widget"',
|
||||||
@@ -314,9 +325,6 @@ IGNORED_ERRORS = {
|
|||||||
'model_enums': [
|
'model_enums': [
|
||||||
"'bool' is not a valid base class",
|
"'bool' is not a valid base class",
|
||||||
],
|
],
|
||||||
'multiple_database': [
|
|
||||||
'Unexpected attribute "extra_arg" for model "Book"',
|
|
||||||
],
|
|
||||||
'null_queries': [
|
'null_queries': [
|
||||||
"Cannot resolve keyword 'foo' into field"
|
"Cannot resolve keyword 'foo' into field"
|
||||||
],
|
],
|
||||||
@@ -372,6 +380,7 @@ IGNORED_ERRORS = {
|
|||||||
'responses': [
|
'responses': [
|
||||||
'Argument 1 to "TextIOWrapper" has incompatible type "HttpResponse"; expected "IO[bytes]"',
|
'Argument 1 to "TextIOWrapper" has incompatible type "HttpResponse"; expected "IO[bytes]"',
|
||||||
'"FileLike" has no attribute "closed"',
|
'"FileLike" has no attribute "closed"',
|
||||||
|
'Argument 1 to "TextIOWrapper" has incompatible type "HttpResponse"; expected "BinaryIO"',
|
||||||
],
|
],
|
||||||
'reverse_lookup': [
|
'reverse_lookup': [
|
||||||
"Cannot resolve keyword 'choice' into field"
|
"Cannot resolve keyword 'choice' into field"
|
||||||
@@ -379,6 +388,9 @@ IGNORED_ERRORS = {
|
|||||||
'settings_tests': [
|
'settings_tests': [
|
||||||
'Argument 1 to "Settings" has incompatible type "Optional[str]"; expected "str"'
|
'Argument 1 to "Settings" has incompatible type "Optional[str]"; expected "str"'
|
||||||
],
|
],
|
||||||
|
'shortcuts': [
|
||||||
|
'error: "Context" has no attribute "request"',
|
||||||
|
],
|
||||||
'signals': [
|
'signals': [
|
||||||
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any, Optional[Any], Any]";'
|
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any, Optional[Any], Any]";'
|
||||||
],
|
],
|
||||||
@@ -390,7 +402,7 @@ IGNORED_ERRORS = {
|
|||||||
'"RequestSite" of "Union[Site, RequestSite]" has no attribute "id"',
|
'"RequestSite" of "Union[Site, RequestSite]" has no attribute "id"',
|
||||||
],
|
],
|
||||||
'syndication_tests': [
|
'syndication_tests': [
|
||||||
'List or tuple expected as variable arguments'
|
'Argument 1 to "add_domain" has incompatible type "*Tuple[object, ...]"',
|
||||||
],
|
],
|
||||||
'sessions_tests': [
|
'sessions_tests': [
|
||||||
'Incompatible types in assignment (expression has type "None", variable has type "int")',
|
'Incompatible types in assignment (expression has type "None", variable has type "int")',
|
||||||
@@ -431,6 +443,7 @@ IGNORED_ERRORS = {
|
|||||||
'test_client_regress': [
|
'test_client_regress': [
|
||||||
'(expression has type "Dict[<nothing>, <nothing>]", variable has type "SessionBase")',
|
'(expression has type "Dict[<nothing>, <nothing>]", variable has type "SessionBase")',
|
||||||
'Unsupported left operand type for + ("None")',
|
'Unsupported left operand type for + ("None")',
|
||||||
|
'Argument 1 to "len" has incompatible type "Context"; expected "Sized"',
|
||||||
],
|
],
|
||||||
'transactions': [
|
'transactions': [
|
||||||
'Incompatible types in assignment (expression has type "Thread", variable has type "Callable[[], Any]")'
|
'Incompatible types in assignment (expression has type "Thread", variable has type "Callable[[], Any]")'
|
||||||
@@ -460,7 +473,7 @@ IGNORED_ERRORS = {
|
|||||||
'No overload variant of "join" matches argument types "str", "None"',
|
'No overload variant of "join" matches argument types "str", "None"',
|
||||||
'Argument 1 to "Archive" has incompatible type "None"; expected "str"',
|
'Argument 1 to "Archive" has incompatible type "None"; expected "str"',
|
||||||
'Argument 1 to "to_path" has incompatible type "int"; expected "Union[Path, str]"',
|
'Argument 1 to "to_path" has incompatible type "int"; expected "Union[Path, str]"',
|
||||||
|
'Cannot infer type argument 1 of "cached_property"',
|
||||||
],
|
],
|
||||||
'view_tests': [
|
'view_tests': [
|
||||||
"Module 'django.views.debug' has no attribute 'Path'",
|
"Module 'django.views.debug' has no attribute 'Path'",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from pytest_mypy.collect import File
|
from pytest_mypy_plugins.collect import File
|
||||||
from pytest_mypy.item import YamlTestItem
|
from pytest_mypy_plugins.item import YamlTestItem
|
||||||
|
|
||||||
|
|
||||||
def django_plugin_hook(test_item: YamlTestItem) -> None:
|
def django_plugin_hook(test_item: YamlTestItem) -> None:
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ from scripts.enabled_test_modules import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
DJANGO_COMMIT_REFS: Dict[str, Tuple[str, str]] = {
|
DJANGO_COMMIT_REFS: Dict[str, Tuple[str, str]] = {
|
||||||
'2.2': ('stable/2.2.x', '86befcc172c23170a720b3e0c06db51a99b3da59'),
|
'2.2': ('stable/2.2.x', '996be04c3ceb456754d9d527d4d708f30727f07e'),
|
||||||
'3.0': ('stable/3.0.x', '6cb30414bc0f83b49afc4cae76d4af5656effe9a')
|
'3.0': ('stable/3.0.x', 'd9f1792c7649e9f946f4a3a35a76bddf5a412b8b')
|
||||||
}
|
}
|
||||||
PROJECT_DIRECTORY = Path(__file__).parent.parent
|
PROJECT_DIRECTORY = Path(__file__).parent.parent
|
||||||
DJANGO_SOURCE_DIRECTORY = PROJECT_DIRECTORY / 'django-sources' # type: Path
|
DJANGO_SOURCE_DIRECTORY = PROJECT_DIRECTORY / 'django-sources' # type: Path
|
||||||
|
|||||||
12
setup.py
12
setup.py
@@ -21,14 +21,14 @@ with open('README.md', 'r') as f:
|
|||||||
readme = f.read()
|
readme = f.read()
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
'mypy>=0.760,<0.770',
|
'mypy>=0.780,<0.790',
|
||||||
'typing-extensions',
|
'typing-extensions',
|
||||||
'django',
|
'django',
|
||||||
]
|
]
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="django-stubs",
|
name="django-stubs",
|
||||||
version="1.4.0",
|
version="1.5.0",
|
||||||
description='Mypy stubs for Django',
|
description='Mypy stubs for Django',
|
||||||
long_description=readme,
|
long_description=readme,
|
||||||
long_description_content_type='text/markdown',
|
long_description_content_type='text/markdown',
|
||||||
@@ -45,6 +45,10 @@ setup(
|
|||||||
'Development Status :: 3 - Alpha',
|
'Development Status :: 3 - Alpha',
|
||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Programming Language :: Python :: 3.6',
|
'Programming Language :: Python :: 3.6',
|
||||||
'Programming Language :: Python :: 3.7'
|
'Programming Language :: Python :: 3.7',
|
||||||
]
|
'Programming Language :: Python :: 3.8'
|
||||||
|
],
|
||||||
|
project_urls={
|
||||||
|
'Release notes': 'https://github.com/typeddjango/django-stubs/releases',
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
126
test-data/typecheck/contrib/admin/test_options.yml
Normal file
126
test-data/typecheck/contrib/admin/test_options.yml
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# "Happy path" test for model admin, trying to cover as many valid
|
||||||
|
# configurations as possible.
|
||||||
|
- case: test_full_admin
|
||||||
|
main: |
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.forms import Form, Textarea
|
||||||
|
from django.db import models
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
from django.contrib.admin.sites import AdminSite
|
||||||
|
from django.db.models.options import Options
|
||||||
|
from django.http.request import HttpRequest
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
|
||||||
|
def an_action(modeladmin: admin.ModelAdmin, request: HttpRequest, queryset: QuerySet) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class A(admin.ModelAdmin):
|
||||||
|
# BaseModelAdmin
|
||||||
|
autocomplete_fields = ("strs",)
|
||||||
|
raw_id_fields = ["strs"]
|
||||||
|
fields = (
|
||||||
|
"a field",
|
||||||
|
["a", "list of", "fields"],
|
||||||
|
)
|
||||||
|
exclude = ("a", "b")
|
||||||
|
fieldsets = [
|
||||||
|
(None, {"fields": ["a", "b"]}),
|
||||||
|
("group", {"fields": ("c",), "classes": ("a",), "description": "foo"}),
|
||||||
|
]
|
||||||
|
form = Form
|
||||||
|
filter_vertical = ("fields",)
|
||||||
|
filter_horizontal = ("plenty", "of", "fields")
|
||||||
|
radio_fields = {
|
||||||
|
"some_field": admin.VERTICAL,
|
||||||
|
"another_field": admin.HORIZONTAL,
|
||||||
|
}
|
||||||
|
prepopulated_fields = {"slug": ("title",)}
|
||||||
|
formfield_overrides = {models.TextField: {"widget": Textarea}}
|
||||||
|
readonly_fields = ("date_modified",)
|
||||||
|
ordering = ("-pk", "date_modified")
|
||||||
|
sortable_by = ["pk"]
|
||||||
|
view_on_site = True
|
||||||
|
show_full_result_count = False
|
||||||
|
|
||||||
|
# ModelAdmin
|
||||||
|
list_display = ("pk",)
|
||||||
|
list_display_links = ("str",)
|
||||||
|
list_filter = ("str", admin.SimpleListFilter, ("str", admin.SimpleListFilter))
|
||||||
|
list_select_related = True
|
||||||
|
list_per_page = 1
|
||||||
|
list_max_show_all = 2
|
||||||
|
list_editable = ("a", "b")
|
||||||
|
search_fields = ("c", "d")
|
||||||
|
date_hirearchy = "f"
|
||||||
|
save_as = False
|
||||||
|
save_as_continue = True
|
||||||
|
save_on_top = False
|
||||||
|
paginator = Paginator
|
||||||
|
presserve_filters = False
|
||||||
|
inlines = (admin.TabularInline, admin.StackedInline)
|
||||||
|
add_form_template = "template"
|
||||||
|
change_form_template = "template"
|
||||||
|
change_list_template = "template"
|
||||||
|
delete_confirmation_template = "template"
|
||||||
|
delete_selected_confirmation_template = "template"
|
||||||
|
object_history_template = "template"
|
||||||
|
popup_response_template = "template"
|
||||||
|
actions = (an_action,)
|
||||||
|
actions_on_top = True
|
||||||
|
actions_on_bottom = False
|
||||||
|
actions_selection_counter = True
|
||||||
|
admin_site = AdminSite()
|
||||||
|
# This test is here to make sure we're not running into a mypy issue which is
|
||||||
|
# worked around using a somewhat complicated _ListOrTuple union type. Once the
|
||||||
|
# issue is solved upstream this test should pass even with the workaround
|
||||||
|
# replaced by a simpler Sequence type.
|
||||||
|
# https://github.com/python/mypy/issues/8921
|
||||||
|
- case: test_fieldset_workaround_regression
|
||||||
|
main: |
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
class A(admin.ModelAdmin):
|
||||||
|
fieldsets = (
|
||||||
|
(None, {
|
||||||
|
'fields': ('name',),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
- case: errors_on_omitting_fields_from_fieldset_opts
|
||||||
|
main: |
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
class A(admin.ModelAdmin):
|
||||||
|
fieldsets = [ # type: ignore
|
||||||
|
(None, {}), # E: Key 'fields' missing for TypedDict "_FieldOpts"
|
||||||
|
]
|
||||||
|
- case: errors_on_invalid_radio_fields
|
||||||
|
main: |
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
class A(admin.ModelAdmin):
|
||||||
|
radio_fields = {"some_field": 0} # E: Dict entry 0 has incompatible type "str": "Literal[0]"; expected "str": "Union[Literal[1], Literal[2]]"
|
||||||
|
|
||||||
|
class B(admin.ModelAdmin):
|
||||||
|
radio_fields = {1: admin.VERTICAL} # E: Dict entry 0 has incompatible type "int": "Literal[2]"; expected "str": "Union[Literal[1], Literal[2]]"
|
||||||
|
- case: errors_for_invalid_formfield_overrides
|
||||||
|
main: |
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.forms import Textarea
|
||||||
|
|
||||||
|
class A(admin.ModelAdmin):
|
||||||
|
formfield_overrides = {
|
||||||
|
"not a field": { # E: Dict entry 0 has incompatible type "str": "Dict[str, Any]"; expected "Type[Field[Any, Any]]": "Mapping[str, Any]"
|
||||||
|
"widget": Textarea
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- case: errors_for_invalid_action_signature
|
||||||
|
main: |
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.http.request import HttpRequest
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
|
||||||
|
def an_action(modeladmin: None) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class A(admin.ModelAdmin):
|
||||||
|
actions = [an_action] # E: List item 0 has incompatible type "Callable[[None], None]"; expected "Callable[[ModelAdmin, HttpRequest, QuerySet[Any]], None]"
|
||||||
43
test-data/typecheck/contrib/auth/test_decorators.yml
Normal file
43
test-data/typecheck/contrib/auth/test_decorators.yml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
- case: login_required_bare
|
||||||
|
main: |
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
@login_required
|
||||||
|
def view_func(request): ...
|
||||||
|
reveal_type(view_func) # N: Revealed type is 'def (request: Any) -> Any'
|
||||||
|
- case: login_required_fancy
|
||||||
|
main: |
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.core.handlers.wsgi import WSGIRequest
|
||||||
|
from django.http import HttpResponse
|
||||||
|
@login_required(redirect_field_name='a', login_url='b')
|
||||||
|
def view_func(request: WSGIRequest, arg: str) -> HttpResponse: ...
|
||||||
|
reveal_type(view_func) # N: Revealed type is 'def (request: django.core.handlers.wsgi.WSGIRequest, arg: builtins.str) -> django.http.response.HttpResponse'
|
||||||
|
- case: login_required_weird
|
||||||
|
main: |
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
# This is non-conventional usage, but covered in Django tests, so we allow it.
|
||||||
|
def view_func(request): ...
|
||||||
|
wrapped_view = login_required(view_func, redirect_field_name='a', login_url='b')
|
||||||
|
reveal_type(wrapped_view) # N: Revealed type is 'def (request: Any) -> Any'
|
||||||
|
- case: login_required_incorrect_return
|
||||||
|
main: |
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
@login_required() # E: Value of type variable "_VIEW" of function cannot be "Callable[[Any], str]"
|
||||||
|
def view_func2(request) -> str: ...
|
||||||
|
- case: user_passes_test
|
||||||
|
main: |
|
||||||
|
from django.contrib.auth.decorators import user_passes_test
|
||||||
|
@user_passes_test(lambda u: u.username.startswith('super'))
|
||||||
|
def view_func(request): ...
|
||||||
|
reveal_type(view_func) # N: Revealed type is 'def (request: Any) -> Any'
|
||||||
|
- case: user_passes_test_bare_is_error
|
||||||
|
main: |
|
||||||
|
from django.http.response import HttpResponse
|
||||||
|
from django.contrib.auth.decorators import user_passes_test
|
||||||
|
@user_passes_test # E: Argument 1 to "user_passes_test" has incompatible type "Callable[[Any], HttpResponse]"; expected "Callable[[AbstractUser], bool]"
|
||||||
|
def view_func(request) -> HttpResponse: ...
|
||||||
|
- case: permission_required
|
||||||
|
main: |
|
||||||
|
from django.contrib.auth.decorators import permission_required
|
||||||
|
@permission_required('polls.can_vote')
|
||||||
|
def view_func(request): ...
|
||||||
28
test-data/typecheck/db/test_transaction.yml
Normal file
28
test-data/typecheck/db/test_transaction.yml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
- case: atomic_bare
|
||||||
|
main: |
|
||||||
|
from django.db.transaction import atomic
|
||||||
|
@atomic
|
||||||
|
def func(x: int) -> list: ...
|
||||||
|
reveal_type(func) # N: Revealed type is 'def (x: builtins.int) -> builtins.list[Any]'
|
||||||
|
- case: atomic_args
|
||||||
|
main: |
|
||||||
|
from django.db.transaction import atomic
|
||||||
|
@atomic(using='bla', savepoint=False)
|
||||||
|
def func(x: int) -> list: ...
|
||||||
|
reveal_type(func) # N: Revealed type is 'def (x: builtins.int) -> builtins.list[Any]'
|
||||||
|
- case: non_atomic_requests_bare
|
||||||
|
main: |
|
||||||
|
from django.db.transaction import non_atomic_requests
|
||||||
|
@non_atomic_requests
|
||||||
|
def view_func(request): ...
|
||||||
|
reveal_type(view_func) # N: Revealed type is 'def (request: Any) -> Any'
|
||||||
|
|
||||||
|
- case: non_atomic_requests_args
|
||||||
|
main: |
|
||||||
|
from django.http.request import HttpRequest
|
||||||
|
from django.http.response import HttpResponse
|
||||||
|
from django.db.transaction import non_atomic_requests
|
||||||
|
@non_atomic_requests
|
||||||
|
def view_func(request: HttpRequest, arg: str) -> HttpResponse: ...
|
||||||
|
reveal_type(view_func) # N: Revealed type is 'def (request: django.http.request.HttpRequest, arg: builtins.str) -> django.http.response.HttpResponse'
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
pass
|
pass
|
||||||
class Book(models.Model):
|
class Book(models.Model):
|
||||||
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
|
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
|
||||||
owner = models.ForeignKey(to='auth.User', on_delete=models.CASCADE)
|
owner = models.ForeignKey(db_column='model_id', to='auth.User', on_delete=models.CASCADE)
|
||||||
|
|
||||||
- case: foreign_key_field_different_order_of_params
|
- case: foreign_key_field_different_order_of_params
|
||||||
main: |
|
main: |
|
||||||
@@ -653,7 +653,7 @@
|
|||||||
- case: related_manager_is_a_subclass_of_default_manager
|
- case: related_manager_is_a_subclass_of_default_manager
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import User
|
from myapp.models import User
|
||||||
reveal_type(User().orders) # N: Revealed type is 'main.Order_RelatedManager'
|
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.get()) # N: Revealed type is 'myapp.models.Order*'
|
||||||
reveal_type(User().orders.manager_method()) # N: Revealed type is 'builtins.int'
|
reveal_type(User().orders.manager_method()) # N: Revealed type is 'builtins.int'
|
||||||
installed_apps:
|
installed_apps:
|
||||||
@@ -663,12 +663,9 @@
|
|||||||
- path: myapp/models.py
|
- path: myapp/models.py
|
||||||
content: |
|
content: |
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from mypy_django_plugin.lib import generics
|
|
||||||
generics.make_classes_generic(models.Manager)
|
|
||||||
|
|
||||||
class User(models.Model):
|
class User(models.Model):
|
||||||
pass
|
pass
|
||||||
class OrderManager(models.Manager['Order']):
|
class OrderManager(models.Manager):
|
||||||
def manager_method(self) -> int:
|
def manager_method(self) -> int:
|
||||||
pass
|
pass
|
||||||
class Order(models.Model):
|
class Order(models.Model):
|
||||||
|
|||||||
@@ -1,46 +1,31 @@
|
|||||||
- case: from_queryset_with_base_manager
|
- case: from_queryset_with_base_manager
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import MyModel
|
from myapp.models import MyModel
|
||||||
|
reveal_type(MyModel().objects) # N: Revealed type is 'myapp.models.MyModel_NewManager[myapp.models.MyModel]'
|
||||||
reveal_type(MyModel().objects) # N: Revealed type is 'myapp.models.NewBaseManager[myapp.models.MyModel*]'
|
|
||||||
reveal_type(MyModel().objects.get()) # N: Revealed type is '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 () -> builtins.str'
|
|
||||||
reveal_type(MyModel().objects.queryset_method()) # N: Revealed type is 'builtins.str'
|
reveal_type(MyModel().objects.queryset_method()) # N: Revealed type is 'builtins.str'
|
||||||
|
|
||||||
reveal_type(MyModel().objects2) # N: Revealed type is 'myapp.models.NewManager[myapp.models.MyModel*]'
|
|
||||||
reveal_type(MyModel().objects2.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
|
||||||
reveal_type(MyModel().objects2.queryset_method) # N: Revealed type is 'def () -> builtins.str'
|
|
||||||
reveal_type(MyModel().objects2.queryset_method()) # N: Revealed type is 'builtins.str'
|
|
||||||
installed_apps:
|
installed_apps:
|
||||||
- myapp
|
- myapp
|
||||||
files:
|
files:
|
||||||
- path: myapp/__init__.py
|
- path: myapp/__init__.py
|
||||||
- path: myapp/models.py
|
- path: myapp/models.py
|
||||||
content: |
|
content: |
|
||||||
from typing import TypeVar
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.manager import BaseManager, Manager
|
from django.db.models.manager import BaseManager
|
||||||
from mypy_django_plugin.lib import generics
|
|
||||||
|
|
||||||
generics.make_classes_generic(models.QuerySet)
|
class ModelQuerySet(models.QuerySet):
|
||||||
_M = TypeVar('_M', bound=models.Model)
|
|
||||||
|
|
||||||
class ModelQuerySet(models.QuerySet[_M]):
|
|
||||||
def queryset_method(self) -> str:
|
def queryset_method(self) -> str:
|
||||||
return 'hello'
|
return 'hello'
|
||||||
|
NewManager = BaseManager.from_queryset(ModelQuerySet)
|
||||||
NewBaseManager = BaseManager.from_queryset(ModelQuerySet)
|
|
||||||
NewManager = Manager.from_queryset(ModelQuerySet)
|
|
||||||
|
|
||||||
generics.make_classes_generic(NewBaseManager, NewManager)
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
class MyModel(models.Model):
|
||||||
objects = NewBaseManager['MyModel']()
|
objects = NewManager()
|
||||||
objects2 = NewManager['MyModel']()
|
|
||||||
|
|
||||||
- case: manager_without_generic_requires_annotation
|
- case: from_queryset_with_manager
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import ModelQuerySet
|
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:
|
installed_apps:
|
||||||
- myapp
|
- myapp
|
||||||
files:
|
files:
|
||||||
@@ -48,27 +33,20 @@
|
|||||||
- path: myapp/models.py
|
- path: myapp/models.py
|
||||||
content: |
|
content: |
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.manager import BaseManager, Manager
|
|
||||||
from mypy_django_plugin.lib import generics
|
|
||||||
|
|
||||||
class ModelQuerySet(models.QuerySet):
|
class ModelQuerySet(models.QuerySet):
|
||||||
def queryset_method(self) -> str:
|
def queryset_method(self) -> str:
|
||||||
return 'hello'
|
return 'hello'
|
||||||
|
|
||||||
NewBaseManager = BaseManager.from_queryset(ModelQuerySet)
|
NewManager = models.Manager.from_queryset(ModelQuerySet)
|
||||||
NewManager = Manager.from_queryset(ModelQuerySet)
|
|
||||||
|
|
||||||
generics.make_classes_generic(NewBaseManager, NewManager)
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
class MyModel(models.Model):
|
||||||
objects = NewBaseManager() # E: Need type annotation for 'objects'
|
objects = NewManager()
|
||||||
|
|
||||||
|
- case: from_queryset_returns_intersection_of_manager_and_queryset
|
||||||
- case: from_queryset_with_custom_manager_as_base
|
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import MyModel, NewManager
|
from myapp.models import MyModel, NewManager
|
||||||
reveal_type(NewManager()) # N: Revealed type is 'myapp.models.NewManager[<nothing>]'
|
reveal_type(NewManager()) # N: Revealed type is 'myapp.models.NewManager'
|
||||||
reveal_type(MyModel.objects) # N: Revealed type is 'myapp.models.NewManager[myapp.models.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 'Any'
|
reveal_type(MyModel.objects.get()) # N: Revealed type is 'Any'
|
||||||
reveal_type(MyModel.objects.manager_only_method()) # N: Revealed type is 'builtins.int'
|
reveal_type(MyModel.objects.manager_only_method()) # N: Revealed type is 'builtins.int'
|
||||||
reveal_type(MyModel.objects.manager_and_queryset_method()) # N: Revealed type is 'builtins.str'
|
reveal_type(MyModel.objects.manager_and_queryset_method()) # N: Revealed type is 'builtins.str'
|
||||||
@@ -79,8 +57,6 @@
|
|||||||
- path: myapp/models.py
|
- path: myapp/models.py
|
||||||
content: |
|
content: |
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from mypy_django_plugin.lib import generics
|
|
||||||
|
|
||||||
class ModelBaseManager(models.Manager):
|
class ModelBaseManager(models.Manager):
|
||||||
def manager_only_method(self) -> int:
|
def manager_only_method(self) -> int:
|
||||||
return 1
|
return 1
|
||||||
@@ -89,17 +65,14 @@
|
|||||||
return 'hello'
|
return 'hello'
|
||||||
|
|
||||||
NewManager = ModelBaseManager.from_queryset(ModelQuerySet)
|
NewManager = ModelBaseManager.from_queryset(ModelQuerySet)
|
||||||
generics.make_classes_generic(NewManager)
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
class MyModel(models.Model):
|
||||||
objects = NewManager['MyModel']()
|
objects = NewManager()
|
||||||
|
|
||||||
|
|
||||||
- case: from_queryset_with_class_name_provided
|
- case: from_queryset_with_class_name_provided
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import MyModel, NewManager
|
from myapp.models import MyModel, NewManager
|
||||||
reveal_type(NewManager()) # N: Revealed type is 'myapp.models.NewManager[<nothing>]'
|
reveal_type(NewManager()) # N: Revealed type is 'myapp.models.NewManager'
|
||||||
reveal_type(MyModel.objects) # N: Revealed type is 'myapp.models.NewManager[myapp.models.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 'Any'
|
reveal_type(MyModel.objects.get()) # N: Revealed type is 'Any'
|
||||||
reveal_type(MyModel.objects.manager_only_method()) # N: Revealed type is 'builtins.int'
|
reveal_type(MyModel.objects.manager_only_method()) # N: Revealed type is 'builtins.int'
|
||||||
reveal_type(MyModel.objects.manager_and_queryset_method()) # N: Revealed type is 'builtins.str'
|
reveal_type(MyModel.objects.manager_and_queryset_method()) # N: Revealed type is 'builtins.str'
|
||||||
@@ -110,8 +83,6 @@
|
|||||||
- path: myapp/models.py
|
- path: myapp/models.py
|
||||||
content: |
|
content: |
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from mypy_django_plugin.lib import generics
|
|
||||||
|
|
||||||
class ModelBaseManager(models.Manager):
|
class ModelBaseManager(models.Manager):
|
||||||
def manager_only_method(self) -> int:
|
def manager_only_method(self) -> int:
|
||||||
return 1
|
return 1
|
||||||
@@ -120,16 +91,13 @@
|
|||||||
return 'hello'
|
return 'hello'
|
||||||
|
|
||||||
NewManager = ModelBaseManager.from_queryset(ModelQuerySet, class_name='NewManager')
|
NewManager = ModelBaseManager.from_queryset(ModelQuerySet, class_name='NewManager')
|
||||||
from mypy_django_plugin.lib import generics
|
|
||||||
generics.make_classes_generic(NewManager)
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
class MyModel(models.Model):
|
||||||
objects = NewManager['MyModel']()
|
objects = NewManager()
|
||||||
|
|
||||||
- case: from_queryset_with_class_inheritance
|
- case: from_queryset_with_class_inheritance
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import MyModel
|
from myapp.models import MyModel
|
||||||
reveal_type(MyModel().objects) # N: Revealed type is 'myapp.models.NewManager[myapp.models.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.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
||||||
reveal_type(MyModel().objects.queryset_method()) # N: Revealed type is 'builtins.str'
|
reveal_type(MyModel().objects.queryset_method()) # N: Revealed type is 'builtins.str'
|
||||||
installed_apps:
|
installed_apps:
|
||||||
@@ -147,17 +115,13 @@
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
NewManager = BaseManager.from_queryset(ModelQuerySet)
|
NewManager = BaseManager.from_queryset(ModelQuerySet)
|
||||||
|
|
||||||
from mypy_django_plugin.lib import generics
|
|
||||||
generics.make_classes_generic(NewManager)
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
class MyModel(models.Model):
|
||||||
objects = NewManager['MyModel']()
|
objects = NewManager()
|
||||||
|
|
||||||
- case: from_queryset_with_manager_in_another_directory_and_imports
|
- case: from_queryset_with_manager_in_another_directory_and_imports
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import MyModel
|
from myapp.models import MyModel
|
||||||
reveal_type(MyModel().objects) # N: Revealed type is 'myapp.managers.NewManager[myapp.models.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.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) # 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]'
|
reveal_type(MyModel().objects.queryset_method('str')) # N: Revealed type is 'Union[builtins.str, None]'
|
||||||
@@ -170,11 +134,8 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from myapp.managers import NewManager
|
from myapp.managers import NewManager
|
||||||
|
|
||||||
from mypy_django_plugin.lib import generics
|
|
||||||
generics.make_classes_generic(NewManager)
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
class MyModel(models.Model):
|
||||||
objects = NewManager['MyModel']()
|
objects = NewManager()
|
||||||
- path: myapp/managers.py
|
- path: myapp/managers.py
|
||||||
content: |
|
content: |
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
@@ -190,7 +151,7 @@
|
|||||||
disable_cache: true
|
disable_cache: true
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import MyModel
|
from myapp.models import MyModel
|
||||||
reveal_type(MyModel().objects) # N: Revealed type is 'myapp.managers.NewManager[myapp.models.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.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) # 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>'
|
reveal_type(MyModel().objects.base_queryset_method(2)) # N: Revealed type is '<nothing>'
|
||||||
@@ -202,12 +163,8 @@
|
|||||||
content: |
|
content: |
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from myapp.managers import NewManager
|
from myapp.managers import NewManager
|
||||||
|
|
||||||
from mypy_django_plugin.lib import generics
|
|
||||||
generics.make_classes_generic(NewManager)
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
class MyModel(models.Model):
|
||||||
objects = NewManager['MyModel']()
|
objects = NewManager()
|
||||||
- path: myapp/managers.py
|
- path: myapp/managers.py
|
||||||
content: |
|
content: |
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@@ -222,59 +179,3 @@
|
|||||||
class BaseQuerySet(models.QuerySet):
|
class BaseQuerySet(models.QuerySet):
|
||||||
def base_queryset_method(self, param: Union[int, str]) -> NoReturn:
|
def base_queryset_method(self, param: Union[int, str]) -> NoReturn:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
|
|
||||||
- case: from_queryset_with_inherited_manager_and_fk_to_auth_contrib
|
|
||||||
disable_cache: true
|
|
||||||
main: |
|
|
||||||
from myapp.base_queryset import BaseQuerySet
|
|
||||||
reveal_type(BaseQuerySet().base_queryset_method) # N: Revealed type is 'def (param: builtins.dict[builtins.str, Union[builtins.int, builtins.str]]) -> Union[builtins.int, builtins.str]'
|
|
||||||
|
|
||||||
from django.contrib.auth.models import Permission
|
|
||||||
reveal_type(Permission().another_models) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.AnotherModelInProjectWithContribAuthM2M]'
|
|
||||||
|
|
||||||
from myapp.managers import NewManager
|
|
||||||
reveal_type(NewManager()) # N: Revealed type is 'myapp.managers.NewManager[<nothing>]'
|
|
||||||
reveal_type(NewManager().base_queryset_method) # N: Revealed type is 'def (param: builtins.dict[builtins.str, Union[builtins.int, builtins.str]]) -> Union[builtins.int, builtins.str]'
|
|
||||||
|
|
||||||
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: builtins.dict[builtins.str, Union[builtins.int, builtins.str]]) -> Union[builtins.int, builtins.str]'
|
|
||||||
installed_apps:
|
|
||||||
- myapp
|
|
||||||
- django.contrib.auth
|
|
||||||
files:
|
|
||||||
- path: myapp/__init__.py
|
|
||||||
- path: myapp/models.py
|
|
||||||
content: |
|
|
||||||
from django.db import models
|
|
||||||
from myapp.managers import NewManager
|
|
||||||
from django.contrib.auth.models import Permission
|
|
||||||
|
|
||||||
from mypy_django_plugin.lib import generics
|
|
||||||
generics.make_classes_generic(NewManager)
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
|
||||||
objects = NewManager['MyModel']()
|
|
||||||
|
|
||||||
class AnotherModelInProjectWithContribAuthM2M(models.Model):
|
|
||||||
permissions = models.ForeignKey(
|
|
||||||
Permission,
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
related_name='another_models'
|
|
||||||
)
|
|
||||||
- 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 Union, Dict
|
|
||||||
from django.db import models
|
|
||||||
class BaseQuerySet(models.QuerySet):
|
|
||||||
def base_queryset_method(self, param: Dict[str, Union[int, str]]) -> Union[int, str]:
|
|
||||||
return param["hello"]
|
|
||||||
@@ -220,9 +220,6 @@
|
|||||||
- path: myapp/models.py
|
- path: myapp/models.py
|
||||||
content: |
|
content: |
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from mypy_django_plugin.lib import generics
|
|
||||||
generics.make_classes_generic(models.QuerySet)
|
|
||||||
|
|
||||||
class TransactionQuerySet(models.QuerySet['Transaction']):
|
class TransactionQuerySet(models.QuerySet['Transaction']):
|
||||||
pass
|
pass
|
||||||
class Transaction(models.Model):
|
class Transaction(models.Model):
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
class Child(Parent):
|
class Child(Parent):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
- case: test_model_objects_attribute_present_in_case_of_model_cls_passed_as_generic_parameter
|
- case: test_model_objects_attribute_present_in_case_of_model_cls_passed_as_generic_parameter
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import Base, MyModel
|
from myapp.models import Base, MyModel
|
||||||
@@ -56,27 +55,23 @@
|
|||||||
def method(self) -> None:
|
def method(self) -> None:
|
||||||
reveal_type(self.model_cls._default_manager) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel]'
|
reveal_type(self.model_cls._default_manager) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel]'
|
||||||
|
|
||||||
|
|
||||||
- case: if_custom_manager_defined_it_is_set_to_default_manager
|
- case: if_custom_manager_defined_it_is_set_to_default_manager
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import MyModel
|
from myapp.models import MyModel
|
||||||
reveal_type(MyModel._default_manager) # N: Revealed type is 'myapp.models.CustomManager[myapp.models.MyModel]'
|
reveal_type(MyModel._default_manager) # N: Revealed type is 'myapp.models.CustomManager[myapp.models.MyModel]'
|
||||||
reveal_type(MyModel._default_manager.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
|
||||||
installed_apps:
|
installed_apps:
|
||||||
- myapp
|
- myapp
|
||||||
files:
|
files:
|
||||||
- path: myapp/__init__.py
|
- path: myapp/__init__.py
|
||||||
- path: myapp/models.py
|
- path: myapp/models.py
|
||||||
content: |
|
content: |
|
||||||
|
from typing import TypeVar
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from mypy_django_plugin.lib import generics
|
_T = TypeVar('_T', bound=models.Model)
|
||||||
generics.make_classes_generic(models.Manager)
|
class CustomManager(models.Manager[_T]):
|
||||||
|
|
||||||
class CustomManager(models.Manager['MyModel']):
|
|
||||||
pass
|
pass
|
||||||
class MyModel(models.Model):
|
class MyModel(models.Model):
|
||||||
manager = CustomManager()
|
manager = CustomManager['MyModel']()
|
||||||
|
|
||||||
|
|
||||||
- case: if_default_manager_name_is_passed_set_default_manager_to_it
|
- case: if_default_manager_name_is_passed_set_default_manager_to_it
|
||||||
main: |
|
main: |
|
||||||
@@ -88,54 +83,40 @@
|
|||||||
- path: myapp/__init__.py
|
- path: myapp/__init__.py
|
||||||
- path: myapp/models.py
|
- path: myapp/models.py
|
||||||
content: |
|
content: |
|
||||||
|
from typing import TypeVar
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from mypy_django_plugin.lib import generics
|
_T = TypeVar('_T', bound=models.Model)
|
||||||
generics.make_classes_generic(models.Manager)
|
class Manager1(models.Manager[_T]):
|
||||||
|
|
||||||
class Manager1(models.Manager['MyModel']):
|
|
||||||
pass
|
pass
|
||||||
class Manager2(models.Manager['MyModel']):
|
class Manager2(models.Manager[_T]):
|
||||||
pass
|
pass
|
||||||
class MyModel(models.Model):
|
class MyModel(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
default_manager_name = 'm2'
|
default_manager_name = 'm2'
|
||||||
m1 = Manager1()
|
m1 = Manager1['MyModel']()
|
||||||
m2 = Manager2()
|
m2 = Manager2['MyModel']()
|
||||||
|
|
||||||
|
- case: test_leave_as_is_if_objects_is_set_and_fill_typevars_with_outer_class
|
||||||
- case: manager_requires_type_annotation_to_be_set_if_generic_is_not_specified
|
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import MyModel
|
from myapp.models import MyUser
|
||||||
reveal_type(MyModel.objects) # N: Revealed type is 'myapp.models.MyManager'
|
reveal_type(MyUser.objects) # N: Revealed type is 'myapp.models.UserManager[myapp.models.MyUser]'
|
||||||
reveal_type(MyModel.objects.get()) # N: Revealed type is 'Any'
|
reveal_type(MyUser.objects.get()) # N: Revealed type is 'myapp.models.MyUser*'
|
||||||
reveal_type(MyModel.objects2) # N: Revealed type is 'myapp.models.MyGenericManager[Any]'
|
reveal_type(MyUser.objects.get_or_404()) # N: Revealed type is 'myapp.models.MyUser'
|
||||||
reveal_type(MyModel.objects2.get()) # N: Revealed type is 'Any'
|
|
||||||
reveal_type(MyModel.objects3) # N: Revealed type is 'myapp.models.MyGenericManager[myapp.models.MyModel]'
|
|
||||||
reveal_type(MyModel.objects3.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
|
||||||
installed_apps:
|
installed_apps:
|
||||||
- myapp
|
- myapp
|
||||||
files:
|
files:
|
||||||
- path: myapp/__init__.py
|
- path: myapp/__init__.py
|
||||||
- path: myapp/models.py
|
- path: myapp/models.py
|
||||||
content: |
|
content: |
|
||||||
from typing import TypeVar
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from mypy_django_plugin.lib import generics
|
class UserManager(models.Manager['MyUser']):
|
||||||
generics.make_classes_generic(models.Manager)
|
def get_or_404(self) -> 'MyUser':
|
||||||
|
|
||||||
class MyManager(models.Manager):
|
|
||||||
pass
|
|
||||||
_T = TypeVar('_T', bound=models.Model)
|
|
||||||
class MyGenericManager(models.Manager[_T]):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class MyModel(models.Model):
|
class MyUser(models.Model):
|
||||||
objects = MyManager()
|
objects = UserManager()
|
||||||
objects2 = MyGenericManager() # E: Need type annotation for 'objects2'
|
|
||||||
objects3: 'MyGenericManager[MyModel]' = MyGenericManager()
|
|
||||||
|
|
||||||
|
|
||||||
- case: model_imported_from_different_file
|
- case: model_imported_from_different_file
|
||||||
main: |
|
main: |
|
||||||
@@ -158,14 +139,13 @@
|
|||||||
class Inventory(models.Model):
|
class Inventory(models.Model):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
- case: managers_that_defined_on_other_models_do_not_influence
|
- case: managers_that_defined_on_other_models_do_not_influence
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import AbstractPerson, Book
|
from myapp.models import AbstractPerson, Book
|
||||||
reveal_type(AbstractPerson.abstract_persons) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.AbstractPerson]'
|
reveal_type(AbstractPerson.abstract_persons) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.AbstractPerson]'
|
||||||
reveal_type(Book.published_objects) # N: Revealed type is 'myapp.models.PublishedBookManager'
|
reveal_type(Book.published_objects) # N: Revealed type is 'myapp.models.PublishedBookManager[myapp.models.Book]'
|
||||||
Book.published_objects.create(title='hello')
|
Book.published_objects.create(title='hello')
|
||||||
reveal_type(Book.annotated_objects) # N: Revealed type is 'myapp.models.AnnotatedBookManager'
|
reveal_type(Book.annotated_objects) # N: Revealed type is 'myapp.models.AnnotatedBookManager[myapp.models.Book]'
|
||||||
Book.annotated_objects.create(title='hello')
|
Book.annotated_objects.create(title='hello')
|
||||||
installed_apps:
|
installed_apps:
|
||||||
- myapp
|
- myapp
|
||||||
@@ -175,9 +155,6 @@
|
|||||||
content: |
|
content: |
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from mypy_django_plugin.lib import generics
|
|
||||||
generics.make_classes_generic(models.Manager)
|
|
||||||
|
|
||||||
class AbstractPerson(models.Model):
|
class AbstractPerson(models.Model):
|
||||||
abstract_persons = models.Manager['AbstractPerson']()
|
abstract_persons = models.Manager['AbstractPerson']()
|
||||||
class PublishedBookManager(models.Manager['Book']):
|
class PublishedBookManager(models.Manager['Book']):
|
||||||
@@ -189,8 +166,7 @@
|
|||||||
published_objects = PublishedBookManager()
|
published_objects = PublishedBookManager()
|
||||||
annotated_objects = AnnotatedBookManager()
|
annotated_objects = AnnotatedBookManager()
|
||||||
|
|
||||||
|
- case: managers_inherited_from_abstract_classes_multiple_inheritance
|
||||||
- case: managers_inherited_from_abstract_classes_multiple_inheritance_do_not_warn_on_liskov
|
|
||||||
main: |
|
main: |
|
||||||
installed_apps:
|
installed_apps:
|
||||||
- myapp
|
- myapp
|
||||||
@@ -199,9 +175,6 @@
|
|||||||
- path: myapp/models.py
|
- path: myapp/models.py
|
||||||
content: |
|
content: |
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from mypy_django_plugin.lib import generics
|
|
||||||
generics.make_classes_generic(models.Manager)
|
|
||||||
|
|
||||||
class CustomManager1(models.Manager['AbstractBase1']):
|
class CustomManager1(models.Manager['AbstractBase1']):
|
||||||
pass
|
pass
|
||||||
class AbstractBase1(models.Model):
|
class AbstractBase1(models.Model):
|
||||||
@@ -220,7 +193,6 @@
|
|||||||
class Child(AbstractBase1, AbstractBase2):
|
class Child(AbstractBase1, AbstractBase2):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
- case: model_has_a_manager_of_the_same_type
|
- case: model_has_a_manager_of_the_same_type
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import UnrelatedModel, MyModel
|
from myapp.models import UnrelatedModel, MyModel
|
||||||
@@ -236,21 +208,59 @@
|
|||||||
- path: myapp/models.py
|
- path: myapp/models.py
|
||||||
content: |
|
content: |
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from mypy_django_plugin.lib import generics
|
|
||||||
generics.make_classes_generic(models.Manager)
|
|
||||||
|
|
||||||
class UnrelatedModel(models.Model):
|
class UnrelatedModel(models.Model):
|
||||||
objects = models.Manager['UnrelatedModel']()
|
objects = models.Manager['UnrelatedModel']()
|
||||||
|
|
||||||
class MyModel(models.Model):
|
class MyModel(models.Model):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
- case: manager_without_annotation_of_the_model_gets_it_from_outer_one
|
||||||
|
main: |
|
||||||
|
from myapp.models import UnrelatedModel2, MyModel2
|
||||||
|
reveal_type(UnrelatedModel2.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.UnrelatedModel2]'
|
||||||
|
reveal_type(UnrelatedModel2.objects.first()) # N: Revealed type is 'Union[myapp.models.UnrelatedModel2*, None]'
|
||||||
|
|
||||||
|
reveal_type(MyModel2.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel2]'
|
||||||
|
reveal_type(MyModel2.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel2*, None]'
|
||||||
|
installed_apps:
|
||||||
|
- myapp
|
||||||
|
files:
|
||||||
|
- path: myapp/__init__.py
|
||||||
|
- path: myapp/models.py
|
||||||
|
content: |
|
||||||
|
from django.db import models
|
||||||
|
class UnrelatedModel2(models.Model):
|
||||||
|
objects = models.Manager()
|
||||||
|
|
||||||
|
class MyModel2(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
- case: inherited_manager_has_the_proper_type_of_model
|
||||||
|
main: |
|
||||||
|
from myapp.models import ParentOfMyModel3, MyModel3
|
||||||
|
reveal_type(ParentOfMyModel3.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.ParentOfMyModel3]'
|
||||||
|
reveal_type(ParentOfMyModel3.objects.first()) # N: Revealed type is 'Union[myapp.models.ParentOfMyModel3*, None]'
|
||||||
|
|
||||||
|
reveal_type(MyModel3.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel3]'
|
||||||
|
reveal_type(MyModel3.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel3*, None]'
|
||||||
|
installed_apps:
|
||||||
|
- myapp
|
||||||
|
files:
|
||||||
|
- path: myapp/__init__.py
|
||||||
|
- path: myapp/models.py
|
||||||
|
content: |
|
||||||
|
from django.db import models
|
||||||
|
class ParentOfMyModel3(models.Model):
|
||||||
|
objects = models.Manager()
|
||||||
|
|
||||||
|
class MyModel3(ParentOfMyModel3):
|
||||||
|
pass
|
||||||
|
|
||||||
- case: inheritance_with_explicit_type_on_child_manager
|
- case: inheritance_with_explicit_type_on_child_manager
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import ParentOfMyModel4, MyModel4
|
from myapp.models import ParentOfMyModel4, MyModel4
|
||||||
reveal_type(ParentOfMyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[Any]'
|
reveal_type(ParentOfMyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.ParentOfMyModel4]'
|
||||||
reveal_type(ParentOfMyModel4.objects.first()) # N: Revealed type is 'Union[Any, None]'
|
reveal_type(ParentOfMyModel4.objects.first()) # N: Revealed type is 'Union[myapp.models.ParentOfMyModel4*, None]'
|
||||||
|
|
||||||
reveal_type(MyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel4]'
|
reveal_type(MyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel4]'
|
||||||
reveal_type(MyModel4.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel4*, None]'
|
reveal_type(MyModel4.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel4*, None]'
|
||||||
@@ -261,22 +271,54 @@
|
|||||||
- path: myapp/models.py
|
- path: myapp/models.py
|
||||||
content: |
|
content: |
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from mypy_django_plugin.lib import generics
|
|
||||||
generics.make_classes_generic(models.Manager)
|
|
||||||
|
|
||||||
class ParentOfMyModel4(models.Model):
|
class ParentOfMyModel4(models.Model):
|
||||||
objects = models.Manager() # E: Need type annotation for 'objects'
|
objects = models.Manager()
|
||||||
|
|
||||||
class MyModel4(ParentOfMyModel4):
|
class MyModel4(ParentOfMyModel4):
|
||||||
objects = models.Manager['MyModel4']()
|
objects = models.Manager['MyModel4']()
|
||||||
|
|
||||||
|
# TODO: make it work someday
|
||||||
|
#- case: inheritance_of_two_models_with_custom_objects_manager
|
||||||
|
# main: |
|
||||||
|
# from myapp.models import MyBaseUser, MyUser
|
||||||
|
# reveal_type(MyBaseUser.objects) # N: Revealed type is 'myapp.models.MyBaseManager[myapp.models.MyBaseUser]'
|
||||||
|
# reveal_type(MyBaseUser.objects.get()) # N: Revealed type is 'myapp.models.MyBaseUser'
|
||||||
|
#
|
||||||
|
# reveal_type(MyUser.objects) # N: Revealed type is 'myapp.models.MyManager[myapp.models.MyUser]'
|
||||||
|
# reveal_type(MyUser.objects.get()) # N: Revealed type is 'myapp.models.MyUser'
|
||||||
|
# installed_apps:
|
||||||
|
# - myapp
|
||||||
|
# files:
|
||||||
|
# - path: myapp/__init__.py
|
||||||
|
# - path: myapp/models.py
|
||||||
|
# content: |
|
||||||
|
# from django.db import models
|
||||||
|
#
|
||||||
|
# class MyBaseManager(models.Manager):
|
||||||
|
# pass
|
||||||
|
# class MyBaseUser(models.Model):
|
||||||
|
# objects = MyBaseManager()
|
||||||
|
#
|
||||||
|
# class MyManager(models.Manager):
|
||||||
|
# pass
|
||||||
|
# class MyUser(MyBaseUser):
|
||||||
|
# objects = MyManager()
|
||||||
|
|
||||||
- case: manager_defined_as_a_nested_class
|
- case: custom_manager_returns_proper_model_types
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import MyModel
|
from myapp.models import User
|
||||||
reveal_type(MyModel.objects) # N: Revealed type is 'myapp.models.MyModel.MyManager'
|
reveal_type(User.objects) # N: Revealed type is 'myapp.models.User_MyManager2[myapp.models.User]'
|
||||||
reveal_type(MyModel.objects.get()) # N: Revealed type is 'myapp.models.MyModel*'
|
reveal_type(User.objects.select_related()) # N: Revealed type is 'myapp.models.User_MyManager2[myapp.models.User]'
|
||||||
reveal_type(MyModel.objects.mymethod()) # N: Revealed type is 'builtins.int'
|
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_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'
|
||||||
installed_apps:
|
installed_apps:
|
||||||
- myapp
|
- myapp
|
||||||
files:
|
files:
|
||||||
@@ -284,11 +326,12 @@
|
|||||||
- path: myapp/models.py
|
- path: myapp/models.py
|
||||||
content: |
|
content: |
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from mypy_django_plugin.lib import generics
|
class MyManager(models.Manager):
|
||||||
generics.make_classes_generic(models.Manager)
|
def get_instance(self) -> int:
|
||||||
|
|
||||||
class MyModel(models.Model):
|
|
||||||
class MyManager(models.Manager['MyModel']):
|
|
||||||
def mymethod(self) -> int:
|
|
||||||
pass
|
pass
|
||||||
|
def get_instance_untyped(self, name):
|
||||||
|
pass
|
||||||
|
class User(models.Model):
|
||||||
|
objects = MyManager()
|
||||||
|
class ChildUser(models.Model):
|
||||||
objects = MyManager()
|
objects = MyManager()
|
||||||
@@ -10,6 +10,12 @@
|
|||||||
reveal_type(User().is_active) # N: Revealed type is 'builtins.bool*'
|
reveal_type(User().is_active) # N: Revealed type is 'builtins.bool*'
|
||||||
reveal_type(User().date_joined) # N: Revealed type is 'datetime.datetime*'
|
reveal_type(User().date_joined) # N: Revealed type is 'datetime.datetime*'
|
||||||
reveal_type(User().last_login) # N: Revealed type is 'Union[datetime.datetime, None]'
|
reveal_type(User().last_login) # N: Revealed type is 'Union[datetime.datetime, None]'
|
||||||
|
reveal_type(User().is_authenticated) # N: Revealed type is 'Literal[True]'
|
||||||
|
reveal_type(User().is_anonymous) # N: Revealed type is 'Literal[False]'
|
||||||
|
|
||||||
|
from django.contrib.auth.models import AnonymousUser
|
||||||
|
reveal_type(AnonymousUser().is_authenticated) # N: Revealed type is 'Literal[False]'
|
||||||
|
reveal_type(AnonymousUser().is_anonymous) # N: Revealed type is 'Literal[True]'
|
||||||
|
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
reveal_type(Permission().name) # N: Revealed type is 'builtins.str*'
|
reveal_type(Permission().name) # N: Revealed type is 'builtins.str*'
|
||||||
|
|||||||
14
test-data/typecheck/models/test_state.yml
Normal file
14
test-data/typecheck/models/test_state.yml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
- case: state_attribute_has_a_type_of_model_state
|
||||||
|
main: |
|
||||||
|
from myapp.models import MyUser
|
||||||
|
user = MyUser(pk=1)
|
||||||
|
reveal_type(user._state) # N: Revealed type is 'django.db.models.base.ModelState'
|
||||||
|
installed_apps:
|
||||||
|
- myapp
|
||||||
|
files:
|
||||||
|
- path: myapp/__init__.py
|
||||||
|
- path: myapp/models.py
|
||||||
|
content: |
|
||||||
|
from django.db import models
|
||||||
|
class MyUser(models.Model):
|
||||||
|
pass
|
||||||
12
test-data/typecheck/test/test_testcase.yml
Normal file
12
test-data/typecheck/test/test_testcase.yml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
- case: testcase_client_attr
|
||||||
|
main: |
|
||||||
|
from django.test.testcases import TestCase
|
||||||
|
|
||||||
|
class ExampleTestCase(TestCase):
|
||||||
|
def test_method(self) -> None:
|
||||||
|
reveal_type(self.client) # N: Revealed type is 'django.test.client.Client'
|
||||||
|
resp = self.client.post('/url', {'doit': 'srs'}, 'application/json', False, True, extra='value')
|
||||||
|
reveal_type(resp.status_code) # N: Revealed type is 'builtins.int'
|
||||||
|
# Attributes monkey-patched by test Client class:
|
||||||
|
resp.json()
|
||||||
|
reveal_type(resp.wsgi_request) # N: Revealed type is 'django.core.handlers.wsgi.WSGIRequest'
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
disable_cache: true
|
disable_cache: true
|
||||||
main: |
|
main: |
|
||||||
from django.http.request import HttpRequest
|
from django.http.request import HttpRequest
|
||||||
reveal_type(HttpRequest().user) # N: Revealed type is 'myapp.models.MyUser'
|
reveal_type(HttpRequest().user) # N: Revealed type is 'Union[myapp.models.MyUser, django.contrib.auth.models.AnonymousUser]'
|
||||||
# check that other fields work ok
|
# check that other fields work ok
|
||||||
reveal_type(HttpRequest().method) # N: Revealed type is 'Union[builtins.str, None]'
|
reveal_type(HttpRequest().method) # N: Revealed type is 'Union[builtins.str, None]'
|
||||||
custom_settings: |
|
custom_settings: |
|
||||||
INSTALLED_APPS = ('django.contrib.contenttypes', 'myapp')
|
INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth', 'myapp')
|
||||||
AUTH_USER_MODEL='myapp.MyUser'
|
AUTH_USER_MODEL='myapp.MyUser'
|
||||||
files:
|
files:
|
||||||
- path: myapp/__init__.py
|
- path: myapp/__init__.py
|
||||||
@@ -15,3 +15,15 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
class MyUser(models.Model):
|
class MyUser(models.Model):
|
||||||
pass
|
pass
|
||||||
|
- case: request_object_user_can_be_descriminated
|
||||||
|
disable_cache: true
|
||||||
|
main: |
|
||||||
|
from django.http.request import HttpRequest
|
||||||
|
request = HttpRequest()
|
||||||
|
reveal_type(request.user) # N: Revealed type is 'Union[django.contrib.auth.models.User, django.contrib.auth.models.AnonymousUser]'
|
||||||
|
if not request.user.is_anonymous:
|
||||||
|
reveal_type(request.user) # N: Revealed type is 'django.contrib.auth.models.User'
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
reveal_type(request.user) # N: Revealed type is 'django.contrib.auth.models.User'
|
||||||
|
custom_settings: |
|
||||||
|
INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth')
|
||||||
|
|||||||
@@ -36,3 +36,24 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
class MyUser(models.Model):
|
class MyUser(models.Model):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
- case: check_render_function_arguments_annotations
|
||||||
|
main: |
|
||||||
|
from typing import Any
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.http.request import HttpRequest
|
||||||
|
|
||||||
|
TestContext = TypedDict("TestContext", {"user": Any})
|
||||||
|
test_context: TestContext = {"user": "test"}
|
||||||
|
reveal_type(test_context) # N: Revealed type is 'TypedDict('main.TestContext', {'user': Any})'
|
||||||
|
reveal_type(render(HttpRequest(), '', test_context)) # N: Revealed type is 'django.http.response.HttpResponse'
|
||||||
|
|
||||||
|
- case: check_redirect_return_annotation
|
||||||
|
main: |
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
reveal_type(redirect(to = '', permanent = True)) # N: Revealed type is 'django.http.response.HttpResponsePermanentRedirect'
|
||||||
|
reveal_type(redirect(to = '', permanent = False)) # N: Revealed type is 'django.http.response.HttpResponseRedirect'
|
||||||
|
|
||||||
|
var = True
|
||||||
|
reveal_type(redirect(to = '', permanent = var)) # N: Revealed type is 'Union[django.http.response.HttpResponseRedirect, django.http.response.HttpResponsePermanentRedirect]'
|
||||||
|
|||||||
20
test-data/typecheck/utils/test_decorators.yml
Normal file
20
test-data/typecheck/utils/test_decorators.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
- case: method_decorator_class
|
||||||
|
main: |
|
||||||
|
from django.views.generic.base import View
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
@method_decorator(login_required, name='dispatch')
|
||||||
|
class TestView(View): ...
|
||||||
|
reveal_type(TestView()) # N: Revealed type is 'main.TestView'
|
||||||
|
- case: method_decorator_function
|
||||||
|
main: |
|
||||||
|
from django.views.generic.base import View
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.http.response import HttpResponse
|
||||||
|
from django.http.request import HttpRequest
|
||||||
|
class TestView(View):
|
||||||
|
@method_decorator(login_required)
|
||||||
|
def dispatch(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
reveal_type(dispatch) # N: Revealed type is 'def (self: main.TestView, request: django.http.request.HttpRequest, *args: Any, **kwargs: Any) -> django.http.response.HttpResponse'
|
||||||
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"
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
digraph {
|
|
||||||
FuncDef [label="My FuncDef"]
|
|
||||||
}
|
|
||||||
Binary file not shown.
Reference in New Issue
Block a user