47 Commits

Author SHA1 Message Date
Nikita Sobolev
3d2534ea8d Closes #392 2020-06-08 10:55:30 +03:00
Kacper
54f5f63e71 Issue 379 (#391)
* pytest-mypy-plugins package newer version

* Revert "pytest-mypy-plugins package newer version"

This reverts commit 871347a86577a5dad867bc751689bbc06d2bcae0.

* update tests ignores for django

* Revert "update tests ignores for django"

This reverts commit 93fc66e311af62cf8cf5b79a72ab723bf3cf060a.

* tests for python 3.8 WIP - initial commit

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-06 14:28:28 +03:00
Kacper
4c5723d368 WIP Issue 388 (#390)
* updated mypy dependency

* update readme

* readme update v2

* pytest-mypy-plugins newer version

* updated pytest_mypy_plugins name

* update ignored errors for typechecking django test suite

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-06 11:35:51 +03:00
Alexander Viklund
7e0e43135d BaseForm.prefix may be None (#389) 2020-06-04 21:24:42 +03:00
Kacper
e05b84e32d Issue 378 (#387)
* proper redirect return type annotations made with Literal

* Mapping instead of Dict type annotation for context in render() with test

* removed Union and Context

* typo

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>

Add __init__ to OrderedSet (#381)

Issue 382 (#384)

* WIP fix, pushed for testing

* added _ prefix for internal types

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>

Fix parameter types for assertJSONEqual/NotEqual (#385)

Add get_supported_language_variant (#386)

Issue 309 (#383)

* added tags for user models

* type test for HttpRequest.user

* test for User and AnonymousUser tags

* httrequest test fix

* checking python version fix for readibility

* Rewrite version check for readability

* Annotate is_authenticated/is_anonymous with Literal-type

* Add auth in INSTALLED_APPS in test

* Fix wrong type assertion in test

* Fix misconception of how branch-testing works

* Remove user from WSGIRequest

* Change HttpRequest-transformer to set user-type to include AnonymousUser

* Add check for anonymous_user_info=None to appease mypy

* Isort transformers/request

* Remove trailing whitespace

* Remove unused import

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>

* fix formatting and unused import

* reformatted again

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-03 23:46:30 +03:00
Alexander Viklund
71751d3795 Issue 309 (#383)
* added tags for user models

* type test for HttpRequest.user

* test for User and AnonymousUser tags

* httrequest test fix

* checking python version fix for readibility

* Rewrite version check for readability

* Annotate is_authenticated/is_anonymous with Literal-type

* Add auth in INSTALLED_APPS in test

* Fix wrong type assertion in test

* Fix misconception of how branch-testing works

* Remove user from WSGIRequest

* Change HttpRequest-transformer to set user-type to include AnonymousUser

* Add check for anonymous_user_info=None to appease mypy

* Isort transformers/request

* Remove trailing whitespace

* Remove unused import

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-03 20:29:19 +03:00
Alexander Viklund
25f92e8e56 Add get_supported_language_variant (#386) 2020-06-03 20:04:19 +03:00
Alexander Viklund
28d47c7e93 Fix parameter types for assertJSONEqual/NotEqual (#385) 2020-06-03 20:03:59 +03:00
Kacper
197cb4058e Issue 382 (#384)
* WIP fix, pushed for testing

* added _ prefix for internal types

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-03 12:58:03 +03:00
Alexander Viklund
dac2b31fb2 Add __init__ to OrderedSet (#381) 2020-06-02 13:56:11 +03:00
Kacper
8d2600136a Issue 355 (#376)
* Mapping instead of Dict type annotation for context in render() with test

* removed Union and Context

* typo

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-01 19:41:57 +03:00
Pavel Savchenko
570772f973 Revert "Allow template render to string helper functions to accept Context (#372)" (#377)
This reverts commit 64cbb0f70e.
2020-06-01 14:34:21 +03:00
Anton Agestam
d5c1bfb12a Increase accuracy of ModelAdmin types (#375)
* Increase accuracy of ModelAdmin types

* Add comment for regression test
2020-05-30 00:24:47 +03:00
Pavel Savchenko
64cbb0f70e Allow template render to string helper functions to accept Context (#372)
* Add failing test case for render_to_string

* Allow Context objects in template render functions
2020-05-23 13:47:39 +03:00
Ville Skyttä
6f5a39625e Add release notes project URL (#365)
Background info at https://github.com/pypa/warehouse/pull/7882
2020-05-05 18:38:11 +03:00
Anthony Ricaud
bf604a0398 Add BaseForm._html_output (#364) 2020-04-30 14:36:55 +03:00
Ashish Bansal (mrphantom)
92c8dfc93f Fix wrong BaseCache typings (#363)
django.core.cache.backends.base.BaseCache were invalid. This commit
fixes them in accordance with [0].

[0] 447980e72a/django/core/cache/backends/base.py (L108)
2020-04-24 12:44:55 +03:00
Jorgen Phillips
c10c55052c Add add_to_class method to Model (#361)
* Add add_to_class method to Model

* Fix @classmethod decorator
2020-04-24 01:11:16 +03:00
Sergey Tikhonov
96914e466b Annotate AdminForm.__init__ (#359) 2020-04-16 18:22:21 +03:00
Onyeka Aghanenu
90ed7f332d Fix Modelform auto_id to accept Boolean type. (#356) 2020-04-13 19:04:18 +03:00
Kacper
a801501151 stub for _build_app_dict (#351)
Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-04-04 20:38:42 +03:00
Sid Mitra
8ea59985df Add RESTRICT and RestrictedError to django.db.models.deletion (#345)
* Add RESTRICT and RestrictedError to django.db.models.deletion

* Add black fomatting changes
2020-04-03 17:02:34 +03:00
Ceesjan Luiten
2964ed53d7 Improve type of BaseForms.files (#350)
This is valid Django, which failed to pass with the old type-defintion:

    class MyForm(Form):
      myparam = MultiFileField(...)

      self.clean(self, *args, **kwargs):
        self.files.getlist('myparam', [])
2020-03-31 10:16:02 +03:00
Stevan Milic
1b9176f994 Add state attribute to Model (#347) (#348)
Add from_db method to Model

Co-authored-by: Stevan Milic <stevan.milic@tradecore.com>
2020-03-30 10:35:40 +03:00
Nikita Sobolev
54d0d018c6 Minor type improvements after linting (#343) 2020-03-19 18:04:51 +03:00
Maxim Kurnikov
1af3a12f2c bump to 1.5.0 2020-03-15 00:59:58 +03:00
Marti Raudsepp
7af89ee6a6 Update to mypy 0.770 (#341) 2020-03-13 16:45:45 +03:00
Marti Raudsepp
afa16bfb74 Make decorator functions transparent to Mypy (#306)
By declaring return type as -> Callable[[_C], _C], Mypy can infer that
the decorated function has also the same arguments and return type as
the original.

View functions are constrained to return HttpResponseBase (or any
subclass of it).

Also added typecheck test coverage to most of the cases.
2020-03-12 00:32:30 +03:00
Marti Raudsepp
f77073157b Fix CI build errors (#339)
* Updated gitpython dependency to fix error:
  ModuleNotFoundError: No module named 'gitdb.utils.compat'
* Updated Django repository git refs because old 3.0.x commit hash gave error:
  stderr: 'fatal: reference is not a tree: 6cb30414bc0f83b49afc4cae76d4af5656effe9a'
* Newer Django version also needs new ignores.
2020-03-12 00:19:51 +03:00
Kevin Marsh
fe3b95c611 Fix input types of humanize functions (#335) 2020-02-27 10:10:53 +03:00
Daniel Hillier
d0f9730c53 Add db.models.fields.Field method stubs for custom Fields (#331)
Add stubs for: pre_save, get_db_prep_value, get_db_prep_save
2020-02-17 10:08:39 +03:00
Leo Shklovskii
0fdd678d65 fix typo in README.md (#328) 2020-02-15 08:28:47 +03:00
Semyon Pupkov
2397065fa6 signing.loads allows use timedelta for max_age (#325)
* signing.loads allows use timedelta

https://github.com/django/django/blob/stable/2.2.x/django/core/signing.py#L191

* fix black
2020-02-14 14:12:58 +03:00
Semyon Pupkov
04023a9f31 Fix readme (#326) 2020-02-13 23:20:58 +03:00
Marti Raudsepp
95e6c94319 Improve test client type stubs (#322)
* Added missing `follow: bool` argument to Client request methods.
* Annotated return types as `HttpResponse` instead of `Any`.
* Made `Client` class inherit from `RequestFactory`, as it does in Django.
* Changed `json()` return type to Any, as it can also be a list.

Wrt (2), these return types were reverted from `HttpResponse` to `Any`
in commit 287c64d6fb. But I suspect that
was simply to silence mypy warnings about "incompatible with supertype
RequestFactory", not because there were any issues with the annotation.

Note that `Client.request()` monkey-patches the `HttpResponse` object
with some additional attributes. Those attributes were already annotated
before, I reordered and added additional comments to make it clear where
they come from.
2020-02-12 18:44:17 +03:00
Brian Helba
d96aee7a8b Fix the type of form.Field.widget (#321)
The `widget` class attribute of `form.Field` expects a `Widget` class, not an instance of `Widget`.

See: https://docs.djangoproject.com/en/3.0/ref/forms/fields/#widget
Also usages at: 3259983f56/django/forms/fields.py (L46)
2020-02-07 12:42:02 +03:00
Nikita Sobolev
2489bb9b04 Adds CONTRIBUTING.md (#319)
* Adds CONTRIBUTING.md

Closes #206 
Refs #274

* Update CONTRIBUTING.md
2020-02-06 15:01:56 +03:00
Nikita Sobolev
3a8f278c88 Update README.md 2020-02-06 11:09:16 +03:00
Nikita Sobolev
85b65b4578 Update README.md 2020-02-06 11:07:23 +03:00
Nikita Sobolev
150e8e862a Update README.md (#317)
* Update README.md

* Update README.md

* Update README.md
2020-02-06 11:03:38 +03:00
Vince Salvino
5b3088a17a Fix cache arg type for django.utils.cache.learn_cache_key() (#316) 2020-02-01 22:03:46 +03:00
Konstantin Alekseev
f89a0fbbaa Cleanup checks framework (#310) 2020-01-30 14:38:23 +03:00
Tatsh
438f8b5829 Add OutputWrapper write() signature (#301)
* Add OutputWrapper write() signature

* Add #type: ignore[override] to OutputWrapper write() signature
2020-01-28 01:34:13 +03:00
Joseph Kahn
836d5acd8f Allow error handlers to be strings (#307)
Based on the docs on this page: https://docs.djangoproject.com/en/3.0/ref/urls/
2020-01-28 01:33:49 +03:00
Cesar Canassa
bfae51e64c Added manager _db (#296) 2020-01-14 17:05:29 +03:00
Daniel Hillier
e3801918e3 Fix incorrect type for django.core.files.File.file (#293) (#294) 2020-01-11 14:27:46 +03:00
Marti Raudsepp
6f296b0a91 Implement @cached_property attribute inference (#292) 2020-01-10 13:57:09 +03:00
91 changed files with 1595 additions and 2229 deletions

View File

@@ -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
View 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
View File

@@ -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
[![Build Status](https://travis-ci.com/typeddjango/django-stubs.svg?branch=master)](https://travis-ci.com/typeddjango/django-stubs) [![Build Status](https://travis-ci.com/typeddjango/django-stubs.svg?branch=master)](https://travis-ci.com/typeddjango/django-stubs)
[![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/) [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
[![Gitter](https://badges.gitter.im/mypy-django/Lobby.svg)](https://gitter.im/mypy-django/Lobby) [![Gitter](https://badges.gitter.im/mypy-django/Lobby.svg)](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.

View File

@@ -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 .

View File

@@ -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]]

View File

@@ -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]] = ...,

View File

@@ -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 = ...

View File

@@ -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(

View File

@@ -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: ...

View File

@@ -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]: ...

View File

@@ -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: ...

View File

@@ -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: ...

View File

@@ -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: ...

View File

@@ -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: ...

View File

@@ -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

View File

@@ -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):

View File

@@ -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: ...

View File

@@ -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,

View File

@@ -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: ...

View File

@@ -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 = ...

View File

@@ -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 = ...,

View File

@@ -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:

View File

@@ -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 (

View File

@@ -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 = ...

View File

@@ -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: ...

View File

@@ -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: ...

View File

@@ -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]: ...

View File

@@ -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]: ...

View File

@@ -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 = ...

View File

@@ -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]

View File

@@ -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] = ...

View File

@@ -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

View File

@@ -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]: ...

View File

@@ -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: ...

View File

@@ -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: ...

View File

@@ -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] = ...

View File

@@ -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: ...

View File

@@ -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: ...

View File

@@ -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:

View File

@@ -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: ...

View File

@@ -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]]]]: ...

View File

@@ -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

View File

@@ -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
View File

@@ -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())))

View File

@@ -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__
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

9
my.gv
View File

@@ -1,9 +0,0 @@
digraph AST {
File
ClassDef
ClassDef -> File
FuncDef
FuncDef -> ClassDef
ExpressionStmt
ExpressionStmt -> File
}

BIN
my.gv.pdf

Binary file not shown.

View 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',

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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}:')

View File

@@ -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])

View File

@@ -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, [])])

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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__

View File

@@ -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

View File

@@ -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'",

View File

@@ -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:

View File

@@ -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

View File

@@ -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',
},
) )

View 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]"

View 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): ...

View 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'

View File

@@ -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):

View File

@@ -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"]

View File

@@ -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):

View File

@@ -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()

View File

@@ -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*'

View 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

View 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'

View File

@@ -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')

View File

@@ -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]'

View 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'

View 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"

View File

@@ -1,3 +0,0 @@
digraph {
FuncDef [label="My FuncDef"]
}

Binary file not shown.