mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-10 22:11:54 +08:00
updated package setup (#485)
* updated package setup * updated to use python 3.9 * fixed test runner * fixed typecheck tests * fixed discrepencies * added override to runner * updated travis * updated pre-commit hooks * updated dep
This commit is contained in:
committed by
GitHub
parent
a3624dec36
commit
44151c485d
18
.gitignore
vendored
18
.gitignore
vendored
@@ -1,15 +1,11 @@
|
|||||||
*.egg-info
|
*.egg-info
|
||||||
__pycache__/
|
.DS_Store
|
||||||
out/
|
|
||||||
/test_sqlite.py
|
|
||||||
/django
|
|
||||||
.idea/
|
.idea/
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
build/
|
|
||||||
dist/
|
|
||||||
pip-wheel-metadata/
|
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
/.envrc
|
.venv/
|
||||||
/.direnv
|
__pycache__/
|
||||||
django-sources/
|
django-source/
|
||||||
.venv/
|
out/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
stubgen/
|
||||||
|
|||||||
39
.pre-commit-config.yaml
Normal file
39
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
default_language_version:
|
||||||
|
python: python3.9
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v3.3.0
|
||||||
|
hooks:
|
||||||
|
- id: check-yaml
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: check-executables-have-shebangs
|
||||||
|
- id: debug-statements
|
||||||
|
- id: check-merge-conflict
|
||||||
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
|
rev: v2.7.3
|
||||||
|
hooks:
|
||||||
|
- id: pyupgrade
|
||||||
|
args: ["--py36-plus"]
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-isort
|
||||||
|
rev: v5.6.4
|
||||||
|
hooks:
|
||||||
|
- id: isort
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 20.8b1
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
- repo: https://gitlab.com/pycqa/flake8
|
||||||
|
rev: 3.8.4
|
||||||
|
hooks:
|
||||||
|
- id: flake8
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: mypy
|
||||||
|
name: mypy
|
||||||
|
entry: mypy
|
||||||
|
language: system
|
||||||
|
types: [ python ]
|
||||||
|
exclude: "scripts/*"
|
||||||
|
args: [ "--cache-dir=/dev/null", "--no-incremental" ]
|
||||||
52
.travis.yml
52
.travis.yml
@@ -2,55 +2,51 @@ language: python
|
|||||||
cache: pip
|
cache: pip
|
||||||
dist: xenial
|
dist: xenial
|
||||||
sudo: required
|
sudo: required
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
- name: Run plugin test suite with python 3.8
|
- name: Typecheck Django 3.0 test suite with python 3.9-dev
|
||||||
python: 3.8
|
python: 3.9-dev
|
||||||
script: 'pytest'
|
script: |
|
||||||
|
python ./scripts/typecheck_tests.py --django_version=3.0
|
||||||
- name: Run plugin test suite with python 3.7
|
|
||||||
python: 3.7
|
|
||||||
script: 'pytest'
|
|
||||||
|
|
||||||
- name: Typecheck Django 3.0 test suite with python 3.8
|
- name: Typecheck Django 3.0 test suite with python 3.8
|
||||||
python: 3.8
|
python: 3.8
|
||||||
script: |
|
script: |
|
||||||
python ./scripts/typecheck_tests.py --django_version=3.0
|
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: |
|
||||||
python ./scripts/typecheck_tests.py --django_version=3.0
|
python ./scripts/typecheck_tests.py --django_version=3.0
|
||||||
|
|
||||||
- name: Typecheck Django 3.0 test suite with python 3.6
|
- name: Typecheck Django 3.0 test suite with python 3.6
|
||||||
python: 3.6
|
python: 3.6
|
||||||
script: |
|
script: |
|
||||||
python ./scripts/typecheck_tests.py --django_version=3.0
|
python ./scripts/typecheck_tests.py --django_version=3.0
|
||||||
|
|
||||||
|
- name: Typecheck Django 2.2 test suite with python 3.9-dev
|
||||||
|
python: 3.9-dev
|
||||||
|
script: |
|
||||||
|
python ./scripts/typecheck_tests.py --django_version=2.2
|
||||||
|
- name: Typecheck Django 2.2 test suite with python 3.8
|
||||||
|
python: 3.8
|
||||||
|
script: |
|
||||||
|
python ./scripts/typecheck_tests.py --django_version=2.2
|
||||||
- name: Typecheck Django 2.2 test suite with python 3.7
|
- name: Typecheck Django 2.2 test suite with python 3.7
|
||||||
python: 3.7
|
python: 3.7
|
||||||
script: |
|
script: |
|
||||||
python ./scripts/typecheck_tests.py --django_version=2.2
|
python ./scripts/typecheck_tests.py --django_version=2.2
|
||||||
|
- name: Typecheck Django 2.2 test suite with python 3.6
|
||||||
|
python: 3.6
|
||||||
|
script: |
|
||||||
|
python ./scripts/typecheck_tests.py --django_version=2.2
|
||||||
|
|
||||||
- name: Mypy for plugin code
|
- name: Lint using pre-commit
|
||||||
python: 3.7
|
python: 3.9-dev
|
||||||
script: 'mypy ./mypy_django_plugin'
|
script:
|
||||||
|
- pre-commit install && pre-commit run --all-files
|
||||||
|
|
||||||
- name: Lint with black
|
- name: Run plugin test suite with python 3.9-dev
|
||||||
python: 3.7
|
python: 3.9-dev
|
||||||
script: 'black --check django-stubs/ setup.py'
|
script: pytest
|
||||||
|
|
||||||
- name: Lint plugin code with flake8
|
|
||||||
python: 3.7
|
|
||||||
script: 'flake8'
|
|
||||||
|
|
||||||
- name: Lint stubs with flake8-pyi and check for unused imports
|
|
||||||
python: 3.7
|
|
||||||
script: 'flake8 --config flake8-pyi.ini'
|
|
||||||
|
|
||||||
- name: Lint plugin code with isort
|
|
||||||
python: 3.7
|
|
||||||
script: 'isort --check --diff'
|
|
||||||
|
|
||||||
before_install: |
|
before_install: |
|
||||||
sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable -y
|
sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable -y
|
||||||
|
|||||||
131
CONTRIBUTING.md
131
CONTRIBUTING.md
@@ -1,111 +1,106 @@
|
|||||||
# How to contribute
|
# Contribution Guide
|
||||||
|
|
||||||
|
This project is open source and community driven. As such we encourage code contributions of all kinds. Some areas you can contribute in:
|
||||||
|
|
||||||
|
1. Improve the stubs
|
||||||
|
2. Sync stubs with the latest version of Django
|
||||||
|
3. Improve plugin code and extend its capabilities
|
||||||
|
4. Write tests
|
||||||
|
5. Update dependencies
|
||||||
|
|
||||||
## Tutorials
|
## Tutorials
|
||||||
|
|
||||||
If you want to start working on this project,
|
If you want to start working on this project, you will need to get familiar with python typings.
|
||||||
you will need to get familiar with these projects:
|
The Mypy documentation offers an excellent resource for this, as well as the python official documentation:
|
||||||
|
|
||||||
|
- [Mypy typing documentation](https://mypy.readthedocs.io/en/stable/#overview-type-system-reference)
|
||||||
|
- [Python official typing documentation](https://docs.python.org/3/library/typing.html)
|
||||||
|
- [Typing in Python](https://inventwithpython.com/blog/2019/11/24/type-hints-for-busy-python-programmers/) article
|
||||||
|
|
||||||
|
Additionally, the following resources might be useful:
|
||||||
|
|
||||||
- [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)
|
- [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
|
- [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
|
- [Testing mypy stubs, plugins, and types](https://sobolevn.me/2019/08/testing-mypy-types) guide
|
||||||
|
- [Awesome Python Typing](https://github.com/typeddjango/awesome-python-typing) list
|
||||||
|
|
||||||
It is also recommended to take a look at these resources:
|
## Dev setup
|
||||||
|
|
||||||
- [Awesome Python Typing](https://github.com/typeddjango/awesome-python-typing)
|
### Repository Setup
|
||||||
|
|
||||||
|
As a first step you will need to fork this repository and clone your fork locally.
|
||||||
|
In order to be able to continously sync your fork with the origin repository's master branch, you will need to set up an upstream master. To do so follow this [official github guide](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/syncing-a-fork).
|
||||||
|
|
||||||
## Dev documentation
|
### Dependency Setup
|
||||||
|
|
||||||
TODO
|
After your repository is setup you will then need to create and activate a git ignored virtual env, e.g.:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
## Dependencies
|
Then install the dev requirements:
|
||||||
|
|
||||||
We use `pip` to manage the dependencies.
|
|
||||||
|
|
||||||
To install them you would need to activate your `virtualenv` and run `install` command:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -r ./dev-requirements.txt
|
pip install -r ./dev-requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Finally, install the pre-commit hooks:
|
||||||
## 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
|
```bash
|
||||||
mypy ./mypy_django_plugin
|
pre-commit install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Testing
|
### Testing and Linting
|
||||||
|
|
||||||
There are unit tests and type-related tests.
|
We use `mypy`, `pytest`, `flake8`, and `black` for quality control. All tools except pytest are executed using pre-commit when you make a commit.
|
||||||
|
To ensure there are not formatting or typing issues in the entire repository you can run:
|
||||||
|
|
||||||
To run unit tests:
|
```bash
|
||||||
|
pre-commit run --all-files
|
||||||
|
```
|
||||||
|
|
||||||
|
NOTE: This command will not only lint but also modify files - so make sure to commit whatever changes you've made before hand.
|
||||||
|
You can also run pre-commit per file or for a specific path, simply replace "--all-files" with a target (see [this guide](https://codeburst.io/tool-your-django-project-pre-commit-hooks-e1799d84551f) for more info).
|
||||||
|
|
||||||
|
To execute the unit tests, simply run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pytest
|
pytest
|
||||||
```
|
```
|
||||||
|
|
||||||
Type-related tests ensure that different Django versions do work correctly.
|
We also test the stubs against the Django's own test suite. This is done in CI but you can also do this locally.
|
||||||
To run type-related tests:
|
To execute the script run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python ./scripts/typecheck_tests.py --django_version=2.2
|
python ./scripts/typecheck_tests.py --django_version 3.0
|
||||||
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
|
### Generating Stubs using Stubgen
|
||||||
|
|
||||||
We use [trunk based](https://trunkbaseddevelopment.com/)
|
The stubs are based on auto-generated code created by Mypy's stubgen tool (see: [the stubgen docs](https://mypy.readthedocs.io/en/stable/stubgen.html)).
|
||||||
development (we also sometimes call it `wemake-git-flow`).
|
To make life easier we have a helper script that auto generates these stubs. To use it you can run:
|
||||||
|
|
||||||
What the point of this method?
|
```bash
|
||||||
|
python ./scripts/stubgen-django.py --django_version 3.1
|
||||||
|
```
|
||||||
|
|
||||||
1. We use protected `master` branch,
|
You can also pass an optional commit hash as a second kwarg to checkout a specific commit, e.g.
|
||||||
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
|
```bash
|
||||||
which frees us from merging hell and long-living branches.
|
python ./scripts/stubgen-django.py --django_version 3.1 --commit_sha <commit_sha>
|
||||||
|
```
|
||||||
|
|
||||||
In this method, the latest version of the app is always in the `master` branch.
|
The output for this is a gitignored folder called "stubgen" in the repo's root.
|
||||||
|
|
||||||
|
## Submission Guidelines
|
||||||
|
|
||||||
## Other help
|
The workflow for contributions is fairly simple:
|
||||||
|
|
||||||
You can contribute by spreading a word about this library.
|
1. fork and setup the repository as in the previous step.
|
||||||
It would also be a huge contribution to write
|
2. create a local branch.
|
||||||
a short article on how you are using this project.
|
3. make whatever changes you want to contribute.
|
||||||
You can also share your best practices with us.
|
4. ensure your contribution does not introduce linting issues or breaks the tests by linting and testing the code.
|
||||||
|
5. make a pull request with an adequate description.
|
||||||
|
|||||||
34
README.md
34
README.md
@@ -1,11 +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"/>
|
||||||
|
|
||||||
# Typesafe Django Framework
|
# pep484 stubs for Django
|
||||||
|
|
||||||
[](https://travis-ci.com/typeddjango/django-stubs)
|
[](https://travis-ci.com/typeddjango/django-stubs)
|
||||||
[](http://mypy-lang.org/)
|
[](http://mypy-lang.org/)
|
||||||
[](https://gitter.im/mypy-django/Lobby)
|
[](https://gitter.im/mypy-django/Lobby)
|
||||||
|
|
||||||
|
|
||||||
This package contains [type stubs](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.
|
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.
|
||||||
|
|
||||||
|
|
||||||
@@ -15,18 +16,13 @@ This package contains [type stubs](https://www.python.org/dev/peps/pep-0561/) an
|
|||||||
pip install django-stubs
|
pip install django-stubs
|
||||||
```
|
```
|
||||||
|
|
||||||
See [Configuration](#configuration) section to get started.
|
To make mypy aware of the plugin, you need to add
|
||||||
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
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]
|
[mypy.plugins.django-stubs]
|
||||||
django_settings_module = "myproject.settings"
|
django_settings_module = "myproject.settings"
|
||||||
```
|
```
|
||||||
@@ -40,7 +36,6 @@ Two things happeining here:
|
|||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
|
||||||
## Version compatibility
|
## Version compatibility
|
||||||
|
|
||||||
We rely on different `django` and `mypy` versions:
|
We rely on different `django` and `mypy` versions:
|
||||||
@@ -73,15 +68,15 @@ But, it does not make any sense to use this project without `mypy`.
|
|||||||
|
|
||||||
### mypy crashes when I run it with this plugin installed
|
### mypy crashes when I run it with this plugin installed
|
||||||
|
|
||||||
The 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()`.
|
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.
|
In other words, if your `manage.py runserver` crashes, mypy will crash too.
|
||||||
You can also run `mypy` with the [`--tb`](https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-show-traceback)
|
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 errors.
|
option to get extra information about the error.
|
||||||
|
|
||||||
### I cannot use QuerySet or Manager with type annotations
|
### I cannot use QuerySet or Manager with type annotations
|
||||||
|
|
||||||
You can get a `TypeError: 'type' object is not subscriptable`
|
You can get a `TypeError: 'type' object is not subscriptable`
|
||||||
when you will try to use `QuerySet[MyModel]` or `Manager[MyModel]`.
|
when you will try to use `QuerySet[MyModel]` 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.
|
This happens because Django classes do not support [`__class_getitem__`](https://www.python.org/dev/peps/pep-0560/#class-getitem) magic method.
|
||||||
@@ -122,5 +117,14 @@ And then use `AuthenticatedHttpRequest` instead of the standard `HttpRequest` fo
|
|||||||
## 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 a more generic typing issue, please refer to <https://github.com/python/mypy> and their Gitter.
|
## Contributing
|
||||||
|
|
||||||
|
This project is open source and community driven. As such we encourage contributions big and small. You can contribute by doing any of the following:
|
||||||
|
|
||||||
|
1. Contribute code (e.g. improve stubs, add plugin capabilities, write tests etc) - to do so please follow the [contribution guide](./CONTRIBUTING.md).
|
||||||
|
2. Assist in code reviews and discussions in issues.
|
||||||
|
3. Identify bugs and issues and report these
|
||||||
|
|
||||||
|
You can always also reach out in gitter to discuss your contributions!
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
black
|
wheel
|
||||||
|
mypy==0.790
|
||||||
|
requests==2.24.0
|
||||||
|
coreapi==2.3.3
|
||||||
|
typing-extensions==3.7.4.3
|
||||||
|
gitpython==3.1.9
|
||||||
|
pre-commit==2.7.1
|
||||||
|
pytest==6.1.1
|
||||||
pytest-mypy-plugins==1.6.1
|
pytest-mypy-plugins==1.6.1
|
||||||
psycopg2-binary
|
psycopg2-binary
|
||||||
flake8==3.7.9
|
|
||||||
flake8-pyi==19.3.0
|
|
||||||
isort==4.3.21
|
|
||||||
gitpython==3.1.0
|
|
||||||
-e .
|
-e .
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class ParallelTestSuite(TestSuite):
|
|||||||
processes: Any = ...
|
processes: Any = ...
|
||||||
failfast: Any = ...
|
failfast: Any = ...
|
||||||
def __init__(self, suite: Any, processes: Any, failfast: bool = ...) -> None: ...
|
def __init__(self, suite: Any, processes: Any, failfast: bool = ...) -> None: ...
|
||||||
def run(self, result: Any): ... # type: ignore
|
def run(self, result: Any): ... # type: ignore[override]
|
||||||
|
|
||||||
class DiscoverRunner:
|
class DiscoverRunner:
|
||||||
test_suite: Any = ...
|
test_suite: Any = ...
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
[flake8]
|
|
||||||
filename =
|
|
||||||
*.pyi
|
|
||||||
exclude =
|
|
||||||
django-sources
|
|
||||||
test-data
|
|
||||||
mypy_django_plugin
|
|
||||||
scripts
|
|
||||||
select = F401, Y
|
|
||||||
max_line_length = 120
|
|
||||||
per-file-ignores =
|
|
||||||
*__init__.pyi: F401
|
|
||||||
base_user.pyi: Y003
|
|
||||||
models.pyi: Y003
|
|
||||||
14
mypy.ini
14
mypy.ini
@@ -1,2 +1,14 @@
|
|||||||
[mypy]
|
[mypy]
|
||||||
warn_unused_ignores = True
|
strict_optional = True
|
||||||
|
ignore_missing_imports = True
|
||||||
|
check_untyped_defs = True
|
||||||
|
warn_no_return = False
|
||||||
|
show_traceback = True
|
||||||
|
allow_redefinition = True
|
||||||
|
incremental = True
|
||||||
|
|
||||||
|
plugins =
|
||||||
|
mypy_django_plugin.main
|
||||||
|
|
||||||
|
[mypy.plugins.django-stubs]
|
||||||
|
django_settings_module = scripts.django_tests_settings
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import (
|
from typing import TYPE_CHECKING, Dict, Iterable, Iterator, Optional, Set, Tuple, Type, Union
|
||||||
TYPE_CHECKING, Dict, Iterable, Iterator, Optional, Set, Tuple, Type, Union,
|
|
||||||
)
|
|
||||||
|
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@@ -26,9 +24,11 @@ from mypy_django_plugin.lib import fullnames, helpers
|
|||||||
try:
|
try:
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
||||||
class ArrayField: # type: ignore
|
class ArrayField: # type: ignore
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from django.apps.registry import Apps # noqa: F401
|
from django.apps.registry import Apps # noqa: F401
|
||||||
from django.conf import LazySettings # noqa: F401
|
from django.conf import LazySettings # noqa: F401
|
||||||
@@ -45,9 +45,9 @@ def temp_environ():
|
|||||||
os.environ.update(environ)
|
os.environ.update(environ)
|
||||||
|
|
||||||
|
|
||||||
def initialize_django(settings_module: str) -> Tuple['Apps', 'LazySettings']:
|
def initialize_django(settings_module: str) -> Tuple["Apps", "LazySettings"]:
|
||||||
with temp_environ():
|
with temp_environ():
|
||||||
os.environ['DJANGO_SETTINGS_MODULE'] = settings_module
|
os.environ["DJANGO_SETTINGS_MODULE"] = settings_module
|
||||||
|
|
||||||
# add current directory to sys.path
|
# add current directory to sys.path
|
||||||
sys.path.append(os.getcwd())
|
sys.path.append(os.getcwd())
|
||||||
@@ -60,8 +60,8 @@ def initialize_django(settings_module: str) -> Tuple['Apps', 'LazySettings']:
|
|||||||
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.apps import apps
|
from django.apps import apps
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
apps.get_models.cache_clear() # type: ignore
|
apps.get_models.cache_clear() # type: ignore
|
||||||
apps.get_swappable_settings_name.cache_clear() # type: ignore
|
apps.get_swappable_settings_name.cache_clear() # type: ignore
|
||||||
@@ -100,15 +100,13 @@ class DjangoContext:
|
|||||||
modules[concrete_model_cls.__module__].add(concrete_model_cls)
|
modules[concrete_model_cls.__module__].add(concrete_model_cls)
|
||||||
# collect abstract=True models
|
# collect abstract=True models
|
||||||
for model_cls in concrete_model_cls.mro()[1:]:
|
for model_cls in concrete_model_cls.mro()[1:]:
|
||||||
if (issubclass(model_cls, Model)
|
if issubclass(model_cls, Model) and hasattr(model_cls, "_meta") and model_cls._meta.abstract:
|
||||||
and hasattr(model_cls, '_meta')
|
|
||||||
and model_cls._meta.abstract):
|
|
||||||
modules[model_cls.__module__].add(model_cls)
|
modules[model_cls.__module__].add(model_cls)
|
||||||
return modules
|
return modules
|
||||||
|
|
||||||
def get_model_class_by_fullname(self, fullname: str) -> Optional[Type[Model]]:
|
def get_model_class_by_fullname(self, fullname: str) -> Optional[Type[Model]]:
|
||||||
# Returns None if Model is abstract
|
# Returns None if Model is abstract
|
||||||
module, _, model_cls_name = fullname.rpartition('.')
|
module, _, model_cls_name = fullname.rpartition(".")
|
||||||
for model_cls in self.model_modules.get(module, set()):
|
for model_cls in self.model_modules.get(module, set()):
|
||||||
if model_cls.__name__ == model_cls_name:
|
if model_cls.__name__ == model_cls_name:
|
||||||
return model_cls
|
return model_cls
|
||||||
@@ -128,7 +126,7 @@ class DjangoContext:
|
|||||||
if isinstance(field, (RelatedField, ForeignObjectRel)):
|
if isinstance(field, (RelatedField, ForeignObjectRel)):
|
||||||
related_model_cls = field.related_model
|
related_model_cls = field.related_model
|
||||||
primary_key_field = self.get_primary_key_field(related_model_cls)
|
primary_key_field = self.get_primary_key_field(related_model_cls)
|
||||||
primary_key_type = self.get_field_get_type(api, primary_key_field, method='init')
|
primary_key_type = self.get_field_get_type(api, primary_key_field, method="init")
|
||||||
|
|
||||||
rel_model_info = helpers.lookup_class_typeinfo(api, related_model_cls)
|
rel_model_info = helpers.lookup_class_typeinfo(api, related_model_cls)
|
||||||
if rel_model_info is None:
|
if rel_model_info is None:
|
||||||
@@ -140,15 +138,14 @@ class DjangoContext:
|
|||||||
field_info = helpers.lookup_class_typeinfo(api, field.__class__)
|
field_info = helpers.lookup_class_typeinfo(api, field.__class__)
|
||||||
if field_info is None:
|
if field_info is None:
|
||||||
return AnyType(TypeOfAny.explicit)
|
return AnyType(TypeOfAny.explicit)
|
||||||
return helpers.get_private_descriptor_type(field_info, '_pyi_lookup_exact_type',
|
return helpers.get_private_descriptor_type(field_info, "_pyi_lookup_exact_type", is_nullable=field.null)
|
||||||
is_nullable=field.null)
|
|
||||||
|
|
||||||
def get_primary_key_field(self, model_cls: Type[Model]) -> Field:
|
def get_primary_key_field(self, model_cls: Type[Model]) -> Field:
|
||||||
for field in model_cls._meta.get_fields():
|
for field in model_cls._meta.get_fields():
|
||||||
if isinstance(field, Field):
|
if isinstance(field, Field):
|
||||||
if field.primary_key:
|
if field.primary_key:
|
||||||
return field
|
return field
|
||||||
raise ValueError('No primary key defined')
|
raise ValueError("No primary key defined")
|
||||||
|
|
||||||
def get_expected_types(self, api: TypeChecker, model_cls: Type[Model], *, method: str) -> Dict[str, MypyType]:
|
def get_expected_types(self, api: TypeChecker, model_cls: Type[Model], *, method: str) -> Dict[str, MypyType]:
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
@@ -158,7 +155,7 @@ class DjangoContext:
|
|||||||
if not model_cls._meta.abstract:
|
if not model_cls._meta.abstract:
|
||||||
primary_key_field = self.get_primary_key_field(model_cls)
|
primary_key_field = self.get_primary_key_field(model_cls)
|
||||||
field_set_type = self.get_field_set_type(api, primary_key_field, method=method)
|
field_set_type = self.get_field_set_type(api, primary_key_field, method=method)
|
||||||
expected_types['pk'] = field_set_type
|
expected_types["pk"] = field_set_type
|
||||||
|
|
||||||
for field in model_cls._meta.get_fields():
|
for field in model_cls._meta.get_fields():
|
||||||
if isinstance(field, Field):
|
if isinstance(field, Field):
|
||||||
@@ -188,11 +185,10 @@ class DjangoContext:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
is_nullable = self.get_field_nullability(field, method)
|
is_nullable = self.get_field_nullability(field, method)
|
||||||
foreign_key_set_type = helpers.get_private_descriptor_type(foreign_key_info,
|
foreign_key_set_type = helpers.get_private_descriptor_type(
|
||||||
'_pyi_private_set_type',
|
foreign_key_info, "_pyi_private_set_type", is_nullable=is_nullable
|
||||||
is_nullable=is_nullable)
|
)
|
||||||
model_set_type = helpers.convert_any_to_type(foreign_key_set_type,
|
model_set_type = helpers.convert_any_to_type(foreign_key_set_type, Instance(related_model_info, []))
|
||||||
Instance(related_model_info, []))
|
|
||||||
|
|
||||||
expected_types[field_name] = model_set_type
|
expected_types[field_name] = model_set_type
|
||||||
|
|
||||||
@@ -200,8 +196,7 @@ class DjangoContext:
|
|||||||
# it's generic, so cannot set specific model
|
# it's generic, so cannot set specific model
|
||||||
field_name = field.name
|
field_name = field.name
|
||||||
gfk_info = helpers.lookup_class_typeinfo(api, field.__class__)
|
gfk_info = helpers.lookup_class_typeinfo(api, field.__class__)
|
||||||
gfk_set_type = helpers.get_private_descriptor_type(gfk_info, '_pyi_private_set_type',
|
gfk_set_type = helpers.get_private_descriptor_type(gfk_info, "_pyi_private_set_type", is_nullable=True)
|
||||||
is_nullable=True)
|
|
||||||
expected_types[field_name] = gfk_set_type
|
expected_types[field_name] = gfk_set_type
|
||||||
|
|
||||||
return expected_types
|
return expected_types
|
||||||
@@ -230,11 +225,10 @@ class DjangoContext:
|
|||||||
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
|
||||||
if method == '__init__':
|
if method == "__init__":
|
||||||
if ((isinstance(field, Field) and field.primary_key)
|
if (isinstance(field, Field) and field.primary_key) or isinstance(field, ForeignKey):
|
||||||
or isinstance(field, ForeignKey)):
|
|
||||||
return True
|
return True
|
||||||
if method == 'create':
|
if method == "create":
|
||||||
if isinstance(field, AutoField):
|
if isinstance(field, AutoField):
|
||||||
return True
|
return True
|
||||||
if isinstance(field, Field) and field.has_default():
|
if isinstance(field, Field) and field.has_default():
|
||||||
@@ -251,8 +245,9 @@ class DjangoContext:
|
|||||||
if field_info is None:
|
if field_info is None:
|
||||||
return AnyType(TypeOfAny.from_error)
|
return AnyType(TypeOfAny.from_error)
|
||||||
|
|
||||||
field_set_type = helpers.get_private_descriptor_type(field_info, '_pyi_private_set_type',
|
field_set_type = helpers.get_private_descriptor_type(
|
||||||
is_nullable=self.get_field_nullability(field, method))
|
field_info, "_pyi_private_set_type", is_nullable=self.get_field_nullability(field, method)
|
||||||
|
)
|
||||||
if isinstance(target_field, ArrayField):
|
if isinstance(target_field, ArrayField):
|
||||||
argument_field_type = self.get_field_set_type(api, target_field.base_field, method=method)
|
argument_field_type = self.get_field_set_type(api, target_field.base_field, method=method)
|
||||||
field_set_type = helpers.convert_any_to_type(field_set_type, argument_field_type)
|
field_set_type = helpers.convert_any_to_type(field_set_type, argument_field_type)
|
||||||
@@ -270,7 +265,7 @@ class DjangoContext:
|
|||||||
if related_model_cls is None:
|
if related_model_cls is None:
|
||||||
return AnyType(TypeOfAny.from_error)
|
return AnyType(TypeOfAny.from_error)
|
||||||
|
|
||||||
if method == 'values':
|
if method == "values":
|
||||||
primary_key_field = self.get_primary_key_field(related_model_cls)
|
primary_key_field = self.get_primary_key_field(related_model_cls)
|
||||||
return self.get_field_get_type(api, primary_key_field, method=method)
|
return self.get_field_get_type(api, primary_key_field, method=method)
|
||||||
|
|
||||||
@@ -280,8 +275,7 @@ class DjangoContext:
|
|||||||
|
|
||||||
return Instance(model_info, [])
|
return Instance(model_info, [])
|
||||||
else:
|
else:
|
||||||
return helpers.get_private_descriptor_type(field_info, '_pyi_private_get_type',
|
return helpers.get_private_descriptor_type(field_info, "_pyi_private_get_type", is_nullable=is_nullable)
|
||||||
is_nullable=is_nullable)
|
|
||||||
|
|
||||||
def get_field_related_model_cls(self, field: Union[RelatedField, ForeignObjectRel]) -> Optional[Type[Model]]:
|
def get_field_related_model_cls(self, field: Union[RelatedField, ForeignObjectRel]) -> Optional[Type[Model]]:
|
||||||
if isinstance(field, RelatedField):
|
if isinstance(field, RelatedField):
|
||||||
@@ -290,26 +284,25 @@ class DjangoContext:
|
|||||||
related_model_cls = field.field.model
|
related_model_cls = field.field.model
|
||||||
|
|
||||||
if isinstance(related_model_cls, str):
|
if isinstance(related_model_cls, str):
|
||||||
if related_model_cls == 'self':
|
if related_model_cls == "self":
|
||||||
# same model
|
# same model
|
||||||
related_model_cls = field.model
|
related_model_cls = field.model
|
||||||
elif '.' not in related_model_cls:
|
elif "." not in related_model_cls:
|
||||||
# same file model
|
# same file model
|
||||||
related_model_fullname = field.model.__module__ + '.' + related_model_cls
|
related_model_fullname = field.model.__module__ + "." + related_model_cls
|
||||||
related_model_cls = self.get_model_class_by_fullname(related_model_fullname)
|
related_model_cls = self.get_model_class_by_fullname(related_model_fullname)
|
||||||
else:
|
else:
|
||||||
related_model_cls = self.apps_registry.get_model(related_model_cls)
|
related_model_cls = self.apps_registry.get_model(related_model_cls)
|
||||||
|
|
||||||
return related_model_cls
|
return related_model_cls
|
||||||
|
|
||||||
def _resolve_field_from_parts(self,
|
def _resolve_field_from_parts(
|
||||||
field_parts: Iterable[str],
|
self, field_parts: Iterable[str], model_cls: Type[Model]
|
||||||
model_cls: Type[Model]
|
) -> Union[Field, ForeignObjectRel]:
|
||||||
) -> Union[Field, ForeignObjectRel]:
|
|
||||||
currently_observed_model = model_cls
|
currently_observed_model = model_cls
|
||||||
field = None
|
field = None
|
||||||
for field_part in field_parts:
|
for field_part in field_parts:
|
||||||
if field_part == 'pk':
|
if field_part == "pk":
|
||||||
field = self.get_primary_key_field(currently_observed_model)
|
field = self.get_primary_key_field(currently_observed_model)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -317,8 +310,7 @@ class DjangoContext:
|
|||||||
if isinstance(field, RelatedField):
|
if isinstance(field, RelatedField):
|
||||||
currently_observed_model = field.related_model
|
currently_observed_model = field.related_model
|
||||||
model_name = currently_observed_model._meta.model_name
|
model_name = currently_observed_model._meta.model_name
|
||||||
if (model_name is not None
|
if model_name is not None and field_part == (model_name + "_id"):
|
||||||
and field_part == (model_name + '_id')):
|
|
||||||
field = self.get_primary_key_field(currently_observed_model)
|
field = self.get_primary_key_field(currently_observed_model)
|
||||||
|
|
||||||
if isinstance(field, ForeignObjectRel):
|
if isinstance(field, ForeignObjectRel):
|
||||||
@@ -368,13 +360,13 @@ class DjangoContext:
|
|||||||
if lookup_base.args and isinstance(lookup_base.args[0], Instance):
|
if lookup_base.args and isinstance(lookup_base.args[0], Instance):
|
||||||
lookup_type: MypyType = lookup_base.args[0]
|
lookup_type: MypyType = lookup_base.args[0]
|
||||||
# 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(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(
|
||||||
is_nullable=field.null)
|
field_info, "_pyi_private_get_type", is_nullable=field.null
|
||||||
|
)
|
||||||
return lookup_type
|
return lookup_type
|
||||||
|
|
||||||
return AnyType(TypeOfAny.explicit)
|
return AnyType(TypeOfAny.explicit)
|
||||||
|
|||||||
@@ -1,39 +1,34 @@
|
|||||||
|
MODEL_CLASS_FULLNAME = "django.db.models.base.Model"
|
||||||
|
FIELD_FULLNAME = "django.db.models.fields.Field"
|
||||||
|
CHAR_FIELD_FULLNAME = "django.db.models.fields.CharField"
|
||||||
|
ARRAY_FIELD_FULLNAME = "django.contrib.postgres.fields.array.ArrayField"
|
||||||
|
AUTO_FIELD_FULLNAME = "django.db.models.fields.AutoField"
|
||||||
|
GENERIC_FOREIGN_KEY_FULLNAME = "django.contrib.contenttypes.fields.GenericForeignKey"
|
||||||
|
FOREIGN_KEY_FULLNAME = "django.db.models.fields.related.ForeignKey"
|
||||||
|
ONETOONE_FIELD_FULLNAME = "django.db.models.fields.related.OneToOneField"
|
||||||
|
MANYTOMANY_FIELD_FULLNAME = "django.db.models.fields.related.ManyToManyField"
|
||||||
|
DUMMY_SETTINGS_BASE_CLASS = "django.conf._DjangoConfLazyObject"
|
||||||
|
|
||||||
MODEL_CLASS_FULLNAME = 'django.db.models.base.Model'
|
QUERYSET_CLASS_FULLNAME = "django.db.models.query.QuerySet"
|
||||||
FIELD_FULLNAME = 'django.db.models.fields.Field'
|
BASE_MANAGER_CLASS_FULLNAME = "django.db.models.manager.BaseManager"
|
||||||
CHAR_FIELD_FULLNAME = 'django.db.models.fields.CharField'
|
MANAGER_CLASS_FULLNAME = "django.db.models.manager.Manager"
|
||||||
ARRAY_FIELD_FULLNAME = 'django.contrib.postgres.fields.array.ArrayField'
|
RELATED_MANAGER_CLASS = "django.db.models.manager.RelatedManager"
|
||||||
AUTO_FIELD_FULLNAME = 'django.db.models.fields.AutoField'
|
|
||||||
GENERIC_FOREIGN_KEY_FULLNAME = 'django.contrib.contenttypes.fields.GenericForeignKey'
|
|
||||||
FOREIGN_KEY_FULLNAME = 'django.db.models.fields.related.ForeignKey'
|
|
||||||
ONETOONE_FIELD_FULLNAME = 'django.db.models.fields.related.OneToOneField'
|
|
||||||
MANYTOMANY_FIELD_FULLNAME = 'django.db.models.fields.related.ManyToManyField'
|
|
||||||
DUMMY_SETTINGS_BASE_CLASS = 'django.conf._DjangoConfLazyObject'
|
|
||||||
|
|
||||||
QUERYSET_CLASS_FULLNAME = 'django.db.models.query.QuerySet'
|
BASEFORM_CLASS_FULLNAME = "django.forms.forms.BaseForm"
|
||||||
BASE_MANAGER_CLASS_FULLNAME = 'django.db.models.manager.BaseManager'
|
FORM_CLASS_FULLNAME = "django.forms.forms.Form"
|
||||||
MANAGER_CLASS_FULLNAME = 'django.db.models.manager.Manager'
|
MODELFORM_CLASS_FULLNAME = "django.forms.models.ModelForm"
|
||||||
RELATED_MANAGER_CLASS = 'django.db.models.manager.RelatedManager'
|
|
||||||
|
|
||||||
BASEFORM_CLASS_FULLNAME = 'django.forms.forms.BaseForm'
|
FORM_MIXIN_CLASS_FULLNAME = "django.views.generic.edit.FormMixin"
|
||||||
FORM_CLASS_FULLNAME = 'django.forms.forms.Form'
|
|
||||||
MODELFORM_CLASS_FULLNAME = 'django.forms.models.ModelForm'
|
|
||||||
|
|
||||||
FORM_MIXIN_CLASS_FULLNAME = 'django.views.generic.edit.FormMixin'
|
|
||||||
|
|
||||||
MANAGER_CLASSES = {
|
MANAGER_CLASSES = {
|
||||||
MANAGER_CLASS_FULLNAME,
|
MANAGER_CLASS_FULLNAME,
|
||||||
BASE_MANAGER_CLASS_FULLNAME,
|
BASE_MANAGER_CLASS_FULLNAME,
|
||||||
}
|
}
|
||||||
|
|
||||||
RELATED_FIELDS_CLASSES = {
|
RELATED_FIELDS_CLASSES = {FOREIGN_KEY_FULLNAME, ONETOONE_FIELD_FULLNAME, MANYTOMANY_FIELD_FULLNAME}
|
||||||
FOREIGN_KEY_FULLNAME,
|
|
||||||
ONETOONE_FIELD_FULLNAME,
|
|
||||||
MANYTOMANY_FIELD_FULLNAME
|
|
||||||
}
|
|
||||||
|
|
||||||
MIGRATION_CLASS_FULLNAME = 'django.db.migrations.migration.Migration'
|
MIGRATION_CLASS_FULLNAME = "django.db.migrations.migration.Migration"
|
||||||
OPTIONS_CLASS_FULLNAME = 'django.db.models.options.Options'
|
OPTIONS_CLASS_FULLNAME = "django.db.models.options.Options"
|
||||||
HTTPREQUEST_CLASS_FULLNAME = 'django.http.request.HttpRequest'
|
HTTPREQUEST_CLASS_FULLNAME = "django.http.request.HttpRequest"
|
||||||
|
|
||||||
F_EXPRESSION_FULLNAME = 'django.db.models.expressions.F'
|
F_EXPRESSION_FULLNAME = "django.db.models.expressions.F"
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import (
|
from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Optional, Set, Tuple, Union
|
||||||
TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Optional, Set, Tuple, Union,
|
|
||||||
)
|
|
||||||
|
|
||||||
from django.db.models.fields import Field
|
from django.db.models.fields import Field
|
||||||
from django.db.models.fields.related import RelatedField
|
from django.db.models.fields.related import RelatedField
|
||||||
@@ -10,11 +8,31 @@ 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 (
|
||||||
GDEF, MDEF, Argument, Block, ClassDef, Expression, FuncDef, MemberExpr, MypyFile, NameExpr, PlaceholderNode,
|
GDEF,
|
||||||
StrExpr, SymbolNode, SymbolTable, SymbolTableNode, TypeInfo, Var,
|
MDEF,
|
||||||
|
Argument,
|
||||||
|
Block,
|
||||||
|
ClassDef,
|
||||||
|
Expression,
|
||||||
|
FuncDef,
|
||||||
|
MemberExpr,
|
||||||
|
MypyFile,
|
||||||
|
NameExpr,
|
||||||
|
PlaceholderNode,
|
||||||
|
StrExpr,
|
||||||
|
SymbolNode,
|
||||||
|
SymbolTable,
|
||||||
|
SymbolTableNode,
|
||||||
|
TypeInfo,
|
||||||
|
Var,
|
||||||
)
|
)
|
||||||
from mypy.plugin import (
|
from mypy.plugin import (
|
||||||
AttributeContext, CheckerPluginInterface, ClassDefContext, DynamicClassDefContext, FunctionContext, MethodContext,
|
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
|
from mypy.semanal import SemanticAnalyzer
|
||||||
@@ -29,7 +47,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
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", {})
|
||||||
|
|
||||||
|
|
||||||
class IncompleteDefnException(Exception):
|
class IncompleteDefnException(Exception):
|
||||||
@@ -37,9 +55,9 @@ class IncompleteDefnException(Exception):
|
|||||||
|
|
||||||
|
|
||||||
def lookup_fully_qualified_sym(fullname: str, all_modules: Dict[str, MypyFile]) -> Optional[SymbolTableNode]:
|
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, cls_name = fullname.rsplit('.', 1)
|
module, cls_name = fullname.rsplit(".", 1)
|
||||||
|
|
||||||
module_file = all_modules.get(module)
|
module_file = all_modules.get(module)
|
||||||
if module_file is None:
|
if module_file is None:
|
||||||
@@ -71,12 +89,11 @@ def lookup_class_typeinfo(api: TypeChecker, klass: type) -> Optional[TypeInfo]:
|
|||||||
|
|
||||||
|
|
||||||
def reparametrize_instance(instance: Instance, new_args: List[MypyType]) -> Instance:
|
def reparametrize_instance(instance: Instance, new_args: List[MypyType]) -> Instance:
|
||||||
return Instance(instance.type, args=new_args,
|
return Instance(instance.type, args=new_args, line=instance.line, column=instance.column)
|
||||||
line=instance.line, column=instance.column)
|
|
||||||
|
|
||||||
|
|
||||||
def get_class_fullname(klass: type) -> str:
|
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]:
|
def get_call_argument_by_name(ctx: Union[FunctionContext, MethodContext], name: str) -> Optional[Expression]:
|
||||||
@@ -115,9 +132,9 @@ def make_optional(typ: MypyType) -> MypyType:
|
|||||||
|
|
||||||
def parse_bool(expr: Expression) -> Optional[bool]:
|
def parse_bool(expr: Expression) -> Optional[bool]:
|
||||||
if isinstance(expr, NameExpr):
|
if isinstance(expr, NameExpr):
|
||||||
if expr.fullname == 'builtins.True':
|
if expr.fullname == "builtins.True":
|
||||||
return True
|
return True
|
||||||
if expr.fullname == 'builtins.False':
|
if expr.fullname == "builtins.False":
|
||||||
return False
|
return False
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -164,27 +181,24 @@ def get_field_lookup_exact_type(api: TypeChecker, field: Field) -> MypyType:
|
|||||||
field_info = lookup_class_typeinfo(api, field.__class__)
|
field_info = lookup_class_typeinfo(api, field.__class__)
|
||||||
if field_info is None:
|
if field_info is None:
|
||||||
return AnyType(TypeOfAny.explicit)
|
return AnyType(TypeOfAny.explicit)
|
||||||
return get_private_descriptor_type(field_info, '_pyi_lookup_exact_type',
|
return get_private_descriptor_type(field_info, "_pyi_lookup_exact_type", is_nullable=field.null)
|
||||||
is_nullable=field.null)
|
|
||||||
|
|
||||||
|
|
||||||
def get_nested_meta_node_for_current_class(info: TypeInfo) -> Optional[TypeInfo]:
|
def get_nested_meta_node_for_current_class(info: TypeInfo) -> Optional[TypeInfo]:
|
||||||
metaclass_sym = info.names.get('Meta')
|
metaclass_sym = info.names.get("Meta")
|
||||||
if metaclass_sym is not None and isinstance(metaclass_sym.node, TypeInfo):
|
if metaclass_sym is not None and isinstance(metaclass_sym.node, TypeInfo):
|
||||||
return metaclass_sym.node
|
return metaclass_sym.node
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def add_new_class_for_module(module: MypyFile,
|
def add_new_class_for_module(
|
||||||
name: str,
|
module: MypyFile, name: str, bases: List[Instance], fields: Optional[Dict[str, MypyType]] = None
|
||||||
bases: List[Instance],
|
) -> TypeInfo:
|
||||||
fields: Optional[Dict[str, MypyType]] = None
|
|
||||||
) -> TypeInfo:
|
|
||||||
new_class_unique_name = checker.gen_unique_name(name, module.names)
|
new_class_unique_name = checker.gen_unique_name(name, module.names)
|
||||||
|
|
||||||
# make new class expression
|
# make new class expression
|
||||||
classdef = ClassDef(new_class_unique_name, Block([]))
|
classdef = ClassDef(new_class_unique_name, Block([]))
|
||||||
classdef.fullname = module.fullname + '.' + new_class_unique_name
|
classdef.fullname = module.fullname + "." + new_class_unique_name
|
||||||
|
|
||||||
# make new TypeInfo
|
# make new TypeInfo
|
||||||
new_typeinfo = TypeInfo(SymbolTable(), classdef, module.fullname)
|
new_typeinfo = TypeInfo(SymbolTable(), classdef, module.fullname)
|
||||||
@@ -197,7 +211,7 @@ def add_new_class_for_module(module: MypyFile,
|
|||||||
for field_name, field_type in fields.items():
|
for field_name, field_type in fields.items():
|
||||||
var = Var(field_name, type=field_type)
|
var = Var(field_name, type=field_type)
|
||||||
var.info = new_typeinfo
|
var.info = new_typeinfo
|
||||||
var._fullname = new_typeinfo.fullname + '.' + field_name
|
var._fullname = new_typeinfo.fullname + "." + field_name
|
||||||
new_typeinfo.names[field_name] = SymbolTableNode(MDEF, var, plugin_generated=True)
|
new_typeinfo.names[field_name] = SymbolTableNode(MDEF, var, plugin_generated=True)
|
||||||
|
|
||||||
classdef.info = new_typeinfo
|
classdef.info = new_typeinfo
|
||||||
@@ -215,18 +229,17 @@ def get_current_module(api: TypeChecker) -> MypyFile:
|
|||||||
return current_module
|
return current_module
|
||||||
|
|
||||||
|
|
||||||
def make_oneoff_named_tuple(api: TypeChecker, name: str, fields: 'OrderedDict[str, MypyType]') -> TupleType:
|
def make_oneoff_named_tuple(api: TypeChecker, name: str, fields: "OrderedDict[str, MypyType]") -> TupleType:
|
||||||
current_module = get_current_module(api)
|
current_module = get_current_module(api)
|
||||||
namedtuple_info = add_new_class_for_module(current_module, name,
|
namedtuple_info = add_new_class_for_module(
|
||||||
bases=[api.named_generic_type('typing.NamedTuple', [])],
|
current_module, name, bases=[api.named_generic_type("typing.NamedTuple", [])], fields=fields
|
||||||
fields=fields)
|
)
|
||||||
return TupleType(list(fields.values()), fallback=Instance(namedtuple_info, []))
|
return TupleType(list(fields.values()), fallback=Instance(namedtuple_info, []))
|
||||||
|
|
||||||
|
|
||||||
def make_tuple(api: 'TypeChecker', fields: List[MypyType]) -> TupleType:
|
def make_tuple(api: "TypeChecker", fields: List[MypyType]) -> TupleType:
|
||||||
# fallback for tuples is any builtins.tuple instance
|
# fallback for tuples is any builtins.tuple instance
|
||||||
fallback = api.named_generic_type('builtins.tuple',
|
fallback = api.named_generic_type("builtins.tuple", [AnyType(TypeOfAny.special_form)])
|
||||||
[AnyType(TypeOfAny.special_form)])
|
|
||||||
return TupleType(fields, fallback=fallback)
|
return TupleType(fields, fallback=fallback)
|
||||||
|
|
||||||
|
|
||||||
@@ -235,8 +248,7 @@ def convert_any_to_type(typ: MypyType, referred_to_type: MypyType) -> MypyType:
|
|||||||
converted_items = []
|
converted_items = []
|
||||||
for item in typ.items:
|
for item in typ.items:
|
||||||
converted_items.append(convert_any_to_type(item, referred_to_type))
|
converted_items.append(convert_any_to_type(item, referred_to_type))
|
||||||
return UnionType.make_union(converted_items,
|
return UnionType.make_union(converted_items, line=typ.line, column=typ.column)
|
||||||
line=typ.line, column=typ.column)
|
|
||||||
if isinstance(typ, Instance):
|
if isinstance(typ, Instance):
|
||||||
args = []
|
args = []
|
||||||
for default_arg in typ.args:
|
for default_arg in typ.args:
|
||||||
@@ -252,21 +264,22 @@ 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]',
|
def make_typeddict(
|
||||||
required_keys: Set[str]) -> TypedDictType:
|
api: CheckerPluginInterface, fields: "OrderedDict[str, MypyType]", required_keys: Set[str]
|
||||||
object_type = api.named_generic_type('mypy_extensions._TypedDict', [])
|
) -> TypedDictType:
|
||||||
|
object_type = api.named_generic_type("mypy_extensions._TypedDict", [])
|
||||||
typed_dict_type = TypedDictType(fields, required_keys=required_keys, fallback=object_type)
|
typed_dict_type = TypedDictType(fields, required_keys=required_keys, fallback=object_type)
|
||||||
return typed_dict_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
|
||||||
|
|
||||||
# support extracting from settings, in general case it's unresolvable yet
|
# support extracting from settings, in general case it's unresolvable yet
|
||||||
if isinstance(attr_expr, MemberExpr):
|
if isinstance(attr_expr, MemberExpr):
|
||||||
member_name = attr_expr.name
|
member_name = attr_expr.name
|
||||||
if isinstance(attr_expr.expr, NameExpr) and attr_expr.expr.fullname == 'django.conf.settings':
|
if isinstance(attr_expr.expr, NameExpr) and attr_expr.expr.fullname == "django.conf.settings":
|
||||||
if hasattr(django_context.settings, member_name):
|
if hasattr(django_context.settings, member_name):
|
||||||
return getattr(django_context.settings, member_name)
|
return getattr(django_context.settings, member_name)
|
||||||
return None
|
return None
|
||||||
@@ -274,27 +287,27 @@ def resolve_string_attribute_value(attr_expr: Expression, django_context: 'Djang
|
|||||||
|
|
||||||
def get_semanal_api(ctx: Union[ClassDefContext, DynamicClassDefContext]) -> SemanticAnalyzer:
|
def get_semanal_api(ctx: Union[ClassDefContext, DynamicClassDefContext]) -> SemanticAnalyzer:
|
||||||
if not isinstance(ctx.api, SemanticAnalyzer):
|
if not isinstance(ctx.api, SemanticAnalyzer):
|
||||||
raise ValueError('Not a SemanticAnalyzer')
|
raise ValueError("Not a SemanticAnalyzer")
|
||||||
return ctx.api
|
return ctx.api
|
||||||
|
|
||||||
|
|
||||||
def get_typechecker_api(ctx: Union[AttributeContext, MethodContext, FunctionContext]) -> TypeChecker:
|
def get_typechecker_api(ctx: Union[AttributeContext, MethodContext, FunctionContext]) -> TypeChecker:
|
||||||
if not isinstance(ctx.api, TypeChecker):
|
if not isinstance(ctx.api, TypeChecker):
|
||||||
raise ValueError('Not a TypeChecker')
|
raise ValueError("Not a TypeChecker")
|
||||||
return ctx.api
|
return ctx.api
|
||||||
|
|
||||||
|
|
||||||
def is_model_subclass_info(info: TypeInfo, django_context: 'DjangoContext') -> bool:
|
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(
|
||||||
or info.has_base(fullnames.MODEL_CLASS_FULLNAME))
|
fullnames.MODEL_CLASS_FULLNAME
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_types_compatible(ctx: Union[FunctionContext, MethodContext],
|
def check_types_compatible(
|
||||||
*, expected_type: MypyType, actual_type: MypyType, error_message: str) -> None:
|
ctx: Union[FunctionContext, MethodContext], *, expected_type: MypyType, actual_type: MypyType, error_message: str
|
||||||
|
) -> None:
|
||||||
api = get_typechecker_api(ctx)
|
api = get_typechecker_api(ctx)
|
||||||
api.check_subtype(actual_type, expected_type,
|
api.check_subtype(actual_type, expected_type, ctx.context, error_message, "got", "expected")
|
||||||
ctx.context, error_message,
|
|
||||||
'got', 'expected')
|
|
||||||
|
|
||||||
|
|
||||||
def add_new_sym_for_info(info: TypeInfo, *, name: str, sym_type: MypyType) -> None:
|
def add_new_sym_for_info(info: TypeInfo, *, name: str, sym_type: MypyType) -> None:
|
||||||
@@ -302,11 +315,10 @@ def add_new_sym_for_info(info: TypeInfo, *, name: str, sym_type: MypyType) -> No
|
|||||||
var = Var(name=name, type=sym_type)
|
var = Var(name=name, type=sym_type)
|
||||||
# var.info: type of the object variable is bound to
|
# var.info: type of the object variable is bound to
|
||||||
var.info = info
|
var.info = info
|
||||||
var._fullname = info.fullname + '.' + name
|
var._fullname = info.fullname + "." + name
|
||||||
var.is_initialized_in_class = True
|
var.is_initialized_in_class = True
|
||||||
var.is_inferred = True
|
var.is_inferred = True
|
||||||
info.names[name] = SymbolTableNode(MDEF, var,
|
info.names[name] = SymbolTableNode(MDEF, var, plugin_generated=True)
|
||||||
plugin_generated=True)
|
|
||||||
|
|
||||||
|
|
||||||
def build_unannotated_method_args(method_node: FuncDef) -> Tuple[List[Argument], MypyType]:
|
def build_unannotated_method_args(method_node: FuncDef) -> Tuple[List[Argument], MypyType]:
|
||||||
@@ -322,8 +334,9 @@ def build_unannotated_method_args(method_node: FuncDef) -> Tuple[List[Argument],
|
|||||||
return prepared_arguments, return_type
|
return prepared_arguments, return_type
|
||||||
|
|
||||||
|
|
||||||
def copy_method_to_another_class(ctx: ClassDefContext, self_type: Instance,
|
def copy_method_to_another_class(
|
||||||
new_method_name: str, method_node: FuncDef) -> None:
|
ctx: ClassDefContext, self_type: Instance, new_method_name: str, method_node: FuncDef
|
||||||
|
) -> None:
|
||||||
semanal_api = get_semanal_api(ctx)
|
semanal_api = get_semanal_api(ctx)
|
||||||
if method_node.type is None:
|
if method_node.type is None:
|
||||||
if not semanal_api.final_iteration:
|
if not semanal_api.final_iteration:
|
||||||
@@ -331,11 +344,7 @@ def copy_method_to_another_class(ctx: ClassDefContext, self_type: Instance,
|
|||||||
return
|
return
|
||||||
|
|
||||||
arguments, return_type = build_unannotated_method_args(method_node)
|
arguments, return_type = build_unannotated_method_args(method_node)
|
||||||
add_method(ctx,
|
add_method(ctx, new_method_name, args=arguments, return_type=return_type, self_type=self_type)
|
||||||
new_method_name,
|
|
||||||
args=arguments,
|
|
||||||
return_type=return_type,
|
|
||||||
self_type=self_type)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
method_type = method_node.type
|
method_type = method_node.type
|
||||||
@@ -345,17 +354,16 @@ def copy_method_to_another_class(ctx: ClassDefContext, self_type: Instance,
|
|||||||
return
|
return
|
||||||
|
|
||||||
arguments = []
|
arguments = []
|
||||||
bound_return_type = semanal_api.anal_type(method_type.ret_type,
|
bound_return_type = semanal_api.anal_type(method_type.ret_type, allow_placeholder=True)
|
||||||
allow_placeholder=True)
|
|
||||||
|
|
||||||
assert bound_return_type is not None
|
assert bound_return_type is not None
|
||||||
|
|
||||||
if isinstance(bound_return_type, PlaceholderNode):
|
if isinstance(bound_return_type, PlaceholderNode):
|
||||||
return
|
return
|
||||||
|
|
||||||
for arg_name, arg_type, original_argument in zip(method_type.arg_names[1:],
|
for arg_name, arg_type, original_argument in zip(
|
||||||
method_type.arg_types[1:],
|
method_type.arg_names[1:], method_type.arg_types[1:], method_node.arguments[1:]
|
||||||
method_node.arguments[1:]):
|
):
|
||||||
bound_arg_type = semanal_api.anal_type(arg_type, allow_placeholder=True)
|
bound_arg_type = semanal_api.anal_type(arg_type, allow_placeholder=True)
|
||||||
if bound_arg_type is None and not semanal_api.final_iteration:
|
if bound_arg_type is None and not semanal_api.final_iteration:
|
||||||
semanal_api.defer()
|
semanal_api.defer()
|
||||||
@@ -366,19 +374,16 @@ def copy_method_to_another_class(ctx: ClassDefContext, self_type: Instance,
|
|||||||
if isinstance(bound_arg_type, PlaceholderNode):
|
if isinstance(bound_arg_type, PlaceholderNode):
|
||||||
return
|
return
|
||||||
|
|
||||||
var = Var(name=original_argument.variable.name,
|
var = Var(name=original_argument.variable.name, type=arg_type)
|
||||||
type=arg_type)
|
|
||||||
var.line = original_argument.variable.line
|
var.line = original_argument.variable.line
|
||||||
var.column = original_argument.variable.column
|
var.column = original_argument.variable.column
|
||||||
argument = Argument(variable=var,
|
argument = Argument(
|
||||||
type_annotation=bound_arg_type,
|
variable=var,
|
||||||
initializer=original_argument.initializer,
|
type_annotation=bound_arg_type,
|
||||||
kind=original_argument.kind)
|
initializer=original_argument.initializer,
|
||||||
|
kind=original_argument.kind,
|
||||||
|
)
|
||||||
argument.set_line(original_argument)
|
argument.set_line(original_argument)
|
||||||
arguments.append(argument)
|
arguments.append(argument)
|
||||||
|
|
||||||
add_method(ctx,
|
add_method(ctx, new_method_name, args=arguments, return_type=bound_return_type, self_type=self_type)
|
||||||
new_method_name,
|
|
||||||
args=arguments,
|
|
||||||
return_type=bound_return_type,
|
|
||||||
self_type=self_type)
|
|
||||||
|
|||||||
@@ -8,28 +8,28 @@ from mypy.modulefinder import mypy_path
|
|||||||
from mypy.nodes import MypyFile, TypeInfo
|
from mypy.nodes import MypyFile, TypeInfo
|
||||||
from mypy.options import Options
|
from mypy.options import Options
|
||||||
from mypy.plugin import (
|
from mypy.plugin import (
|
||||||
AttributeContext, ClassDefContext, DynamicClassDefContext, FunctionContext, MethodContext, Plugin,
|
AttributeContext,
|
||||||
|
ClassDefContext,
|
||||||
|
DynamicClassDefContext,
|
||||||
|
FunctionContext,
|
||||||
|
MethodContext,
|
||||||
|
Plugin,
|
||||||
)
|
)
|
||||||
from mypy.types import Type as MypyType
|
from mypy.types import Type as MypyType
|
||||||
|
|
||||||
import mypy_django_plugin.transformers.orm_lookups
|
import mypy_django_plugin.transformers.orm_lookups
|
||||||
from mypy_django_plugin.django.context import DjangoContext
|
from mypy_django_plugin.django.context import DjangoContext
|
||||||
from mypy_django_plugin.lib import fullnames, helpers
|
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.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
|
||||||
|
|
||||||
|
|
||||||
def transform_model_class(ctx: ClassDefContext,
|
def transform_model_class(ctx: ClassDefContext, django_context: DjangoContext) -> None:
|
||||||
django_context: DjangoContext) -> None:
|
|
||||||
sym = ctx.api.lookup_fully_qualified_or_none(fullnames.MODEL_CLASS_FULLNAME)
|
sym = ctx.api.lookup_fully_qualified_or_none(fullnames.MODEL_CLASS_FULLNAME)
|
||||||
|
|
||||||
if sym is not None and isinstance(sym.node, TypeInfo):
|
if sym is not None and isinstance(sym.node, TypeInfo):
|
||||||
helpers.get_django_metadata(sym.node)['model_bases'][ctx.cls.fullname] = 1
|
helpers.get_django_metadata(sym.node)["model_bases"][ctx.cls.fullname] = 1
|
||||||
else:
|
else:
|
||||||
if not ctx.api.final_iteration:
|
if not ctx.api.final_iteration:
|
||||||
ctx.api.defer()
|
ctx.api.defer()
|
||||||
@@ -41,7 +41,7 @@ def transform_model_class(ctx: ClassDefContext,
|
|||||||
def transform_form_class(ctx: ClassDefContext) -> None:
|
def transform_form_class(ctx: ClassDefContext) -> None:
|
||||||
sym = ctx.api.lookup_fully_qualified_or_none(fullnames.BASEFORM_CLASS_FULLNAME)
|
sym = ctx.api.lookup_fully_qualified_or_none(fullnames.BASEFORM_CLASS_FULLNAME)
|
||||||
if sym is not None and isinstance(sym.node, TypeInfo):
|
if sym is not None and isinstance(sym.node, TypeInfo):
|
||||||
helpers.get_django_metadata(sym.node)['baseform_bases'][ctx.cls.fullname] = 1
|
helpers.get_django_metadata(sym.node)["baseform_bases"][ctx.cls.fullname] = 1
|
||||||
|
|
||||||
forms.make_meta_nested_class_inherit_from_any(ctx)
|
forms.make_meta_nested_class_inherit_from_any(ctx)
|
||||||
|
|
||||||
@@ -49,11 +49,10 @@ def transform_form_class(ctx: ClassDefContext) -> None:
|
|||||||
def add_new_manager_base(ctx: ClassDefContext) -> None:
|
def add_new_manager_base(ctx: ClassDefContext) -> None:
|
||||||
sym = ctx.api.lookup_fully_qualified_or_none(fullnames.MANAGER_CLASS_FULLNAME)
|
sym = ctx.api.lookup_fully_qualified_or_none(fullnames.MANAGER_CLASS_FULLNAME)
|
||||||
if sym is not None and isinstance(sym.node, TypeInfo):
|
if sym is not None and isinstance(sym.node, TypeInfo):
|
||||||
helpers.get_django_metadata(sym.node)['manager_bases'][ctx.cls.fullname] = 1
|
helpers.get_django_metadata(sym.node)["manager_bases"][ctx.cls.fullname] = 1
|
||||||
|
|
||||||
|
|
||||||
def extract_django_settings_module(config_file_path: Optional[str]) -> str:
|
def extract_django_settings_module(config_file_path: Optional[str]) -> str:
|
||||||
|
|
||||||
def exit(error_type: int) -> NoReturn:
|
def exit(error_type: int) -> NoReturn:
|
||||||
"""Using mypy's argument parser, raise `SystemExit` to fail hard if validation fails.
|
"""Using mypy's argument parser, raise `SystemExit` to fail hard if validation fails.
|
||||||
|
|
||||||
@@ -69,24 +68,28 @@ def extract_django_settings_module(config_file_path: Optional[str]) -> str:
|
|||||||
[mypy.plugins.django_stubs]
|
[mypy.plugins.django_stubs]
|
||||||
django_settings_module: str (required)
|
django_settings_module: str (required)
|
||||||
...
|
...
|
||||||
""".replace("\n" + 8 * " ", "\n")
|
""".replace(
|
||||||
handler = CapturableArgumentParser(prog='(django-stubs) mypy', usage=usage)
|
"\n" + 8 * " ", "\n"
|
||||||
messages = {1: 'mypy config file is not specified or found',
|
)
|
||||||
2: 'no section [mypy.plugins.django-stubs]',
|
handler = CapturableArgumentParser(prog="(django-stubs) mypy", usage=usage)
|
||||||
3: 'the setting is not provided'}
|
messages = {
|
||||||
|
1: "mypy config file is not specified or found",
|
||||||
|
2: "no section [mypy.plugins.django-stubs]",
|
||||||
|
3: "the setting is not provided",
|
||||||
|
}
|
||||||
handler.error("'django_settings_module' is not set: " + messages[error_type])
|
handler.error("'django_settings_module' is not set: " + messages[error_type])
|
||||||
|
|
||||||
parser = configparser.ConfigParser()
|
parser = configparser.ConfigParser()
|
||||||
try:
|
try:
|
||||||
parser.read_file(open(cast(str, config_file_path), 'r'), source=config_file_path)
|
parser.read_file(open(cast(str, config_file_path)), source=config_file_path)
|
||||||
except (IsADirectoryError, OSError):
|
except (IsADirectoryError, OSError):
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
section = 'mypy.plugins.django-stubs'
|
section = "mypy.plugins.django-stubs"
|
||||||
if not parser.has_section(section):
|
if not parser.has_section(section):
|
||||||
exit(2)
|
exit(2)
|
||||||
settings = parser.get(section, 'django_settings_module', fallback=None) or exit(3)
|
settings = parser.get(section, "django_settings_module", fallback=None) or exit(3)
|
||||||
return cast(str, settings).strip('\'"')
|
return cast(str, settings).strip("'\"")
|
||||||
|
|
||||||
|
|
||||||
class NewSemanalDjangoPlugin(Plugin):
|
class NewSemanalDjangoPlugin(Plugin):
|
||||||
@@ -102,34 +105,41 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
def _get_current_queryset_bases(self) -> Dict[str, int]:
|
def _get_current_queryset_bases(self) -> Dict[str, int]:
|
||||||
model_sym = self.lookup_fully_qualified(fullnames.QUERYSET_CLASS_FULLNAME)
|
model_sym = self.lookup_fully_qualified(fullnames.QUERYSET_CLASS_FULLNAME)
|
||||||
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
|
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
|
||||||
return (helpers.get_django_metadata(model_sym.node)
|
return helpers.get_django_metadata(model_sym.node).setdefault(
|
||||||
.setdefault('queryset_bases', {fullnames.QUERYSET_CLASS_FULLNAME: 1}))
|
"queryset_bases", {fullnames.QUERYSET_CLASS_FULLNAME: 1}
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _get_current_manager_bases(self) -> Dict[str, int]:
|
def _get_current_manager_bases(self) -> Dict[str, int]:
|
||||||
model_sym = self.lookup_fully_qualified(fullnames.MANAGER_CLASS_FULLNAME)
|
model_sym = self.lookup_fully_qualified(fullnames.MANAGER_CLASS_FULLNAME)
|
||||||
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
|
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
|
||||||
return (helpers.get_django_metadata(model_sym.node)
|
return helpers.get_django_metadata(model_sym.node).setdefault(
|
||||||
.setdefault('manager_bases', {fullnames.MANAGER_CLASS_FULLNAME: 1}))
|
"manager_bases", {fullnames.MANAGER_CLASS_FULLNAME: 1}
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _get_current_model_bases(self) -> Dict[str, int]:
|
def _get_current_model_bases(self) -> Dict[str, int]:
|
||||||
model_sym = self.lookup_fully_qualified(fullnames.MODEL_CLASS_FULLNAME)
|
model_sym = self.lookup_fully_qualified(fullnames.MODEL_CLASS_FULLNAME)
|
||||||
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
|
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
|
||||||
return helpers.get_django_metadata(model_sym.node).setdefault('model_bases',
|
return helpers.get_django_metadata(model_sym.node).setdefault(
|
||||||
{fullnames.MODEL_CLASS_FULLNAME: 1})
|
"model_bases", {fullnames.MODEL_CLASS_FULLNAME: 1}
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _get_current_form_bases(self) -> Dict[str, int]:
|
def _get_current_form_bases(self) -> Dict[str, int]:
|
||||||
model_sym = self.lookup_fully_qualified(fullnames.BASEFORM_CLASS_FULLNAME)
|
model_sym = self.lookup_fully_qualified(fullnames.BASEFORM_CLASS_FULLNAME)
|
||||||
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
|
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
|
||||||
return (helpers.get_django_metadata(model_sym.node)
|
return helpers.get_django_metadata(model_sym.node).setdefault(
|
||||||
.setdefault('baseform_bases', {fullnames.BASEFORM_CLASS_FULLNAME: 1,
|
"baseform_bases",
|
||||||
fullnames.FORM_CLASS_FULLNAME: 1,
|
{
|
||||||
fullnames.MODELFORM_CLASS_FULLNAME: 1}))
|
fullnames.BASEFORM_CLASS_FULLNAME: 1,
|
||||||
|
fullnames.FORM_CLASS_FULLNAME: 1,
|
||||||
|
fullnames.MODELFORM_CLASS_FULLNAME: 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -144,17 +154,16 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
|
|
||||||
def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
|
def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
|
||||||
# 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)]
|
||||||
|
|
||||||
# for values / values_list
|
# for values / values_list
|
||||||
if file.fullname == 'django.db.models':
|
if file.fullname == "django.db.models":
|
||||||
return [self._new_dependency('mypy_extensions'), self._new_dependency('typing')]
|
return [self._new_dependency("mypy_extensions"), self._new_dependency("typing")]
|
||||||
|
|
||||||
# for `get_user_model()`
|
# for `get_user_model()`
|
||||||
if self.django_context.settings:
|
if self.django_context.settings:
|
||||||
if (file.fullname == 'django.contrib.auth'
|
if file.fullname == "django.contrib.auth" or file.fullname in {"django.http", "django.http.request"}:
|
||||||
or file.fullname in {'django.http', 'django.http.request'}):
|
|
||||||
auth_user_model_name = self.django_context.settings.AUTH_USER_MODEL
|
auth_user_model_name = self.django_context.settings.AUTH_USER_MODEL
|
||||||
try:
|
try:
|
||||||
auth_user_module = self.django_context.apps_registry.get_model(auth_user_model_name).__module__
|
auth_user_module = self.django_context.apps_registry.get_model(auth_user_model_name).__module__
|
||||||
@@ -186,9 +195,8 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
deps.add(self._new_dependency(related_model_module))
|
deps.add(self._new_dependency(related_model_module))
|
||||||
return list(deps)
|
return list(deps)
|
||||||
|
|
||||||
def get_function_hook(self, fullname: str
|
def get_function_hook(self, fullname: str) -> Optional[Callable[[FunctionContext], MypyType]]:
|
||||||
) -> Optional[Callable[[FunctionContext], MypyType]]:
|
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()
|
manager_bases = self._get_current_manager_bases()
|
||||||
@@ -204,46 +212,48 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
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
|
||||||
|
|
||||||
def get_method_hook(self, fullname: str
|
def get_method_hook(self, fullname: str) -> Optional[Callable[[MethodContext], MypyType]]:
|
||||||
) -> Optional[Callable[[MethodContext], MypyType]]:
|
class_fullname, _, method_name = fullname.rpartition(".")
|
||||||
class_fullname, _, method_name = fullname.rpartition('.')
|
if method_name == "get_form_class":
|
||||||
if method_name == 'get_form_class':
|
|
||||||
info = self._get_typeinfo_or_none(class_fullname)
|
info = self._get_typeinfo_or_none(class_fullname)
|
||||||
if info and info.has_base(fullnames.FORM_MIXIN_CLASS_FULLNAME):
|
if info and info.has_base(fullnames.FORM_MIXIN_CLASS_FULLNAME):
|
||||||
return forms.extract_proper_type_for_get_form_class
|
return forms.extract_proper_type_for_get_form_class
|
||||||
|
|
||||||
if method_name == 'get_form':
|
if method_name == "get_form":
|
||||||
info = self._get_typeinfo_or_none(class_fullname)
|
info = self._get_typeinfo_or_none(class_fullname)
|
||||||
if info and info.has_base(fullnames.FORM_MIXIN_CLASS_FULLNAME):
|
if info and info.has_base(fullnames.FORM_MIXIN_CLASS_FULLNAME):
|
||||||
return forms.extract_proper_type_for_get_form
|
return forms.extract_proper_type_for_get_form
|
||||||
|
|
||||||
if method_name == 'values':
|
if method_name == "values":
|
||||||
info = self._get_typeinfo_or_none(class_fullname)
|
info = self._get_typeinfo_or_none(class_fullname)
|
||||||
if info and info.has_base(fullnames.QUERYSET_CLASS_FULLNAME):
|
if info and info.has_base(fullnames.QUERYSET_CLASS_FULLNAME):
|
||||||
return partial(querysets.extract_proper_type_queryset_values, django_context=self.django_context)
|
return partial(querysets.extract_proper_type_queryset_values, django_context=self.django_context)
|
||||||
|
|
||||||
if method_name == 'values_list':
|
if method_name == "values_list":
|
||||||
info = self._get_typeinfo_or_none(class_fullname)
|
info = self._get_typeinfo_or_none(class_fullname)
|
||||||
if info and info.has_base(fullnames.QUERYSET_CLASS_FULLNAME):
|
if info and info.has_base(fullnames.QUERYSET_CLASS_FULLNAME):
|
||||||
return partial(querysets.extract_proper_type_queryset_values_list, django_context=self.django_context)
|
return partial(querysets.extract_proper_type_queryset_values_list, django_context=self.django_context)
|
||||||
|
|
||||||
if method_name == 'get_field':
|
if method_name == "get_field":
|
||||||
info = self._get_typeinfo_or_none(class_fullname)
|
info = self._get_typeinfo_or_none(class_fullname)
|
||||||
if info and info.has_base(fullnames.OPTIONS_CLASS_FULLNAME):
|
if info and info.has_base(fullnames.OPTIONS_CLASS_FULLNAME):
|
||||||
return partial(meta.return_proper_field_type_from_get_field, django_context=self.django_context)
|
return partial(meta.return_proper_field_type_from_get_field, django_context=self.django_context)
|
||||||
|
|
||||||
manager_classes = self._get_current_manager_bases()
|
manager_classes = self._get_current_manager_bases()
|
||||||
if class_fullname in manager_classes and method_name == 'create':
|
if class_fullname in manager_classes and method_name == "create":
|
||||||
return partial(init_create.redefine_and_typecheck_model_create, django_context=self.django_context)
|
return partial(init_create.redefine_and_typecheck_model_create, django_context=self.django_context)
|
||||||
if class_fullname in manager_classes and method_name in {'filter', 'get', 'exclude'}:
|
if class_fullname in manager_classes and method_name in {"filter", "get", "exclude"}:
|
||||||
return partial(mypy_django_plugin.transformers.orm_lookups.typecheck_queryset_filter,
|
return partial(
|
||||||
django_context=self.django_context)
|
mypy_django_plugin.transformers.orm_lookups.typecheck_queryset_filter,
|
||||||
|
django_context=self.django_context,
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_base_class_hook(self, fullname: str
|
def get_base_class_hook(self, fullname: str) -> Optional[Callable[[ClassDefContext], None]]:
|
||||||
) -> Optional[Callable[[ClassDefContext], None]]:
|
if (
|
||||||
if (fullname in self.django_context.all_registered_model_class_fullnames
|
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 partial(transform_model_class, django_context=self.django_context)
|
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():
|
||||||
@@ -253,22 +263,19 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
return transform_form_class
|
return transform_form_class
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_attribute_hook(self, fullname: str
|
def get_attribute_hook(self, fullname: str) -> Optional[Callable[[AttributeContext], MypyType]]:
|
||||||
) -> Optional[Callable[[AttributeContext], MypyType]]:
|
class_name, _, attr_name = fullname.rpartition(".")
|
||||||
class_name, _, attr_name = fullname.rpartition('.')
|
|
||||||
if class_name == fullnames.DUMMY_SETTINGS_BASE_CLASS:
|
if class_name == fullnames.DUMMY_SETTINGS_BASE_CLASS:
|
||||||
return partial(settings.get_type_of_settings_attribute,
|
return partial(settings.get_type_of_settings_attribute, django_context=self.django_context)
|
||||||
django_context=self.django_context)
|
|
||||||
|
|
||||||
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)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_dynamic_class_hook(self, fullname: str
|
def get_dynamic_class_hook(self, fullname: str) -> Optional[Callable[[DynamicClassDefContext], None]]:
|
||||||
) -> Optional[Callable[[DynamicClassDefContext], None]]:
|
if fullname.endswith("from_queryset"):
|
||||||
if fullname.endswith('from_queryset'):
|
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 create_new_manager_class_from_from_queryset_method
|
return create_new_manager_class_from_from_queryset_method
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ 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 = 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_model_subclass_info(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
|
||||||
@@ -60,8 +59,7 @@ def fill_descriptor_types_for_related_field(ctx: FunctionContext, django_context
|
|||||||
|
|
||||||
# self reference with abstract=True on the model where ForeignKey is defined
|
# self reference with abstract=True on the model where ForeignKey is defined
|
||||||
current_model_cls = current_field.model
|
current_model_cls = current_field.model
|
||||||
if (current_model_cls._meta.abstract
|
if current_model_cls._meta.abstract and current_model_cls == related_model_cls:
|
||||||
and current_model_cls == related_model_cls):
|
|
||||||
# for all derived non-abstract classes, set variable with this name to
|
# for all derived non-abstract classes, set variable with this name to
|
||||||
# __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:
|
||||||
@@ -69,11 +67,10 @@ def fill_descriptor_types_for_related_field(ctx: FunctionContext, django_context
|
|||||||
derived_model_info = helpers.lookup_class_typeinfo(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(
|
||||||
set_type=fk_ref_type, get_type=fk_ref_type)
|
default_related_field_type, set_type=fk_ref_type, get_type=fk_ref_type
|
||||||
helpers.add_new_sym_for_info(derived_model_info,
|
)
|
||||||
name=current_field.name,
|
helpers.add_new_sym_for_info(derived_model_info, name=current_field.name, sym_type=derived_fk_type)
|
||||||
sym_type=derived_fk_type)
|
|
||||||
|
|
||||||
related_model = related_model_cls
|
related_model = related_model_cls
|
||||||
related_model_to_set = related_model_cls
|
related_model_to_set = related_model_cls
|
||||||
@@ -97,16 +94,14 @@ def fill_descriptor_types_for_related_field(ctx: FunctionContext, django_context
|
|||||||
related_model_to_set_type = Instance(related_model_to_set_info, []) # type: ignore
|
related_model_to_set_type = Instance(related_model_to_set_info, []) # type: ignore
|
||||||
|
|
||||||
# replace Any with referred_to_type
|
# replace Any with referred_to_type
|
||||||
return reparametrize_related_field_type(default_related_field_type,
|
return reparametrize_related_field_type(
|
||||||
set_type=related_model_to_set_type,
|
default_related_field_type, set_type=related_model_to_set_type, get_type=related_model_type
|
||||||
get_type=related_model_type)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_field_descriptor_types(field_info: TypeInfo, is_nullable: bool) -> Tuple[MypyType, MypyType]:
|
def get_field_descriptor_types(field_info: TypeInfo, is_nullable: bool) -> Tuple[MypyType, MypyType]:
|
||||||
set_type = helpers.get_private_descriptor_type(field_info, '_pyi_private_set_type',
|
set_type = helpers.get_private_descriptor_type(field_info, "_pyi_private_set_type", is_nullable=is_nullable)
|
||||||
is_nullable=is_nullable)
|
get_type = helpers.get_private_descriptor_type(field_info, "_pyi_private_get_type", is_nullable=is_nullable)
|
||||||
get_type = helpers.get_private_descriptor_type(field_info, '_pyi_private_get_type',
|
|
||||||
is_nullable=is_nullable)
|
|
||||||
return set_type, get_type
|
return set_type, get_type
|
||||||
|
|
||||||
|
|
||||||
@@ -114,7 +109,7 @@ 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 = 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
|
||||||
|
|
||||||
@@ -125,7 +120,7 @@ def set_descriptor_types_for_field(ctx: FunctionContext) -> Instance:
|
|||||||
def determine_type_of_array_field(ctx: FunctionContext, django_context: DjangoContext) -> 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 = 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
|
||||||
|
|
||||||
@@ -142,8 +137,7 @@ def transform_into_proper_return_type(ctx: FunctionContext, django_context: Djan
|
|||||||
assert isinstance(default_return_type, Instance)
|
assert isinstance(default_return_type, Instance)
|
||||||
|
|
||||||
outer_model_info = 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_model_subclass_info(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)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ def make_meta_nested_class_inherit_from_any(ctx: ClassDefContext) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def get_specified_form_class(object_type: Instance) -> Optional[TypeType]:
|
def get_specified_form_class(object_type: Instance) -> Optional[TypeType]:
|
||||||
form_class_sym = object_type.type.get('form_class')
|
form_class_sym = object_type.type.get("form_class")
|
||||||
if form_class_sym and isinstance(form_class_sym.type, CallableType):
|
if form_class_sym and isinstance(form_class_sym.type, CallableType):
|
||||||
return TypeType(form_class_sym.type.ret_type)
|
return TypeType(form_class_sym.type.ret_type)
|
||||||
return None
|
return None
|
||||||
@@ -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 = 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)
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ from mypy_django_plugin.django.context import DjangoContext
|
|||||||
from mypy_django_plugin.lib import helpers
|
from mypy_django_plugin.lib import helpers
|
||||||
|
|
||||||
|
|
||||||
def get_actual_types(ctx: Union[MethodContext, FunctionContext],
|
def get_actual_types(
|
||||||
expected_keys: List[str]) -> List[Tuple[str, MypyType]]:
|
ctx: Union[MethodContext, FunctionContext], expected_keys: List[str]
|
||||||
|
) -> List[Tuple[str, MypyType]]:
|
||||||
actual_types = []
|
actual_types = []
|
||||||
# positionals
|
# positionals
|
||||||
for pos, (actual_name, actual_type) in enumerate(zip(ctx.arg_names[0], ctx.arg_types[0])):
|
for pos, (actual_name, actual_type) in enumerate(zip(ctx.arg_names[0], ctx.arg_types[0])):
|
||||||
if actual_name is None:
|
if actual_name is None:
|
||||||
if ctx.callee_arg_names[0] == 'kwargs':
|
if ctx.callee_arg_names[0] == "kwargs":
|
||||||
# unpacked dict as kwargs is not supported
|
# unpacked dict as kwargs is not supported
|
||||||
continue
|
continue
|
||||||
actual_name = expected_keys[pos]
|
actual_name = expected_keys[pos]
|
||||||
@@ -30,23 +31,23 @@ def get_actual_types(ctx: Union[MethodContext, FunctionContext],
|
|||||||
return actual_types
|
return actual_types
|
||||||
|
|
||||||
|
|
||||||
def typecheck_model_method(ctx: Union[FunctionContext, MethodContext], django_context: DjangoContext,
|
def typecheck_model_method(
|
||||||
model_cls: Type[Model], method: str) -> MypyType:
|
ctx: Union[FunctionContext, MethodContext], django_context: DjangoContext, model_cls: Type[Model], method: str
|
||||||
|
) -> MypyType:
|
||||||
typechecker_api = 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"]
|
||||||
|
|
||||||
for actual_name, actual_type in get_actual_types(ctx, expected_keys):
|
for actual_name, actual_type in get_actual_types(ctx, expected_keys):
|
||||||
if actual_name not in expected_types:
|
if actual_name not in expected_types:
|
||||||
ctx.api.fail('Unexpected attribute "{}" for model "{}"'.format(actual_name,
|
ctx.api.fail(f'Unexpected attribute "{actual_name}" for model "{model_cls.__name__}"', ctx.context)
|
||||||
model_cls.__name__),
|
|
||||||
ctx.context)
|
|
||||||
continue
|
continue
|
||||||
helpers.check_types_compatible(ctx,
|
helpers.check_types_compatible(
|
||||||
expected_type=expected_types[actual_name],
|
ctx,
|
||||||
actual_type=actual_type,
|
expected_type=expected_types[actual_name],
|
||||||
error_message='Incompatible type for "{}" of "{}"'.format(actual_name,
|
actual_type=actual_type,
|
||||||
model_cls.__name__))
|
error_message=f'Incompatible type for "{actual_name}" of "{model_cls.__name__}"',
|
||||||
|
)
|
||||||
|
|
||||||
return ctx.default_return_type
|
return ctx.default_return_type
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ def redefine_and_typecheck_model_init(ctx: FunctionContext, django_context: Djan
|
|||||||
if model_cls is None:
|
if model_cls is None:
|
||||||
return ctx.default_return_type
|
return ctx.default_return_type
|
||||||
|
|
||||||
return typecheck_model_method(ctx, django_context, model_cls, '__init__')
|
return typecheck_model_method(ctx, django_context, model_cls, "__init__")
|
||||||
|
|
||||||
|
|
||||||
def redefine_and_typecheck_model_create(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
|
def redefine_and_typecheck_model_create(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
|
||||||
@@ -72,4 +73,4 @@ def redefine_and_typecheck_model_create(ctx: MethodContext, django_context: Djan
|
|||||||
if model_cls is None:
|
if model_cls is None:
|
||||||
return ctx.default_return_type
|
return ctx.default_return_type
|
||||||
|
|
||||||
return typecheck_model_method(ctx, django_context, model_cls, 'create')
|
return typecheck_model_method(ctx, django_context, model_cls, "create")
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
from mypy.nodes import (
|
from mypy.nodes import GDEF, FuncDef, MemberExpr, NameExpr, RefExpr, StrExpr, SymbolTableNode, TypeInfo
|
||||||
GDEF, FuncDef, MemberExpr, NameExpr, RefExpr, StrExpr, SymbolTableNode, TypeInfo,
|
|
||||||
)
|
|
||||||
from mypy.plugin import ClassDefContext, DynamicClassDefContext
|
from mypy.plugin import ClassDefContext, DynamicClassDefContext
|
||||||
from mypy.types import AnyType, Instance, TypeOfAny
|
from mypy.types import AnyType, Instance, TypeOfAny
|
||||||
|
|
||||||
@@ -21,16 +19,15 @@ def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefConte
|
|||||||
return
|
return
|
||||||
|
|
||||||
assert isinstance(base_manager_info, TypeInfo)
|
assert isinstance(base_manager_info, TypeInfo)
|
||||||
new_manager_info = semanal_api.basic_new_typeinfo(ctx.name,
|
new_manager_info = semanal_api.basic_new_typeinfo(
|
||||||
basetype_or_fallback=Instance(base_manager_info,
|
ctx.name, basetype_or_fallback=Instance(base_manager_info, [AnyType(TypeOfAny.unannotated)])
|
||||||
[AnyType(TypeOfAny.unannotated)]))
|
)
|
||||||
new_manager_info.line = ctx.call.line
|
new_manager_info.line = ctx.call.line
|
||||||
new_manager_info.defn.line = ctx.call.line
|
new_manager_info.defn.line = ctx.call.line
|
||||||
new_manager_info.metaclass_type = new_manager_info.calculate_metaclass_type()
|
new_manager_info.metaclass_type = new_manager_info.calculate_metaclass_type()
|
||||||
|
|
||||||
current_module = semanal_api.cur_mod_node
|
current_module = semanal_api.cur_mod_node
|
||||||
current_module.names[ctx.name] = SymbolTableNode(GDEF, new_manager_info,
|
current_module.names[ctx.name] = SymbolTableNode(GDEF, new_manager_info, plugin_generated=True)
|
||||||
plugin_generated=True)
|
|
||||||
passed_queryset = ctx.call.args[0]
|
passed_queryset = ctx.call.args[0]
|
||||||
assert isinstance(passed_queryset, NameExpr)
|
assert isinstance(passed_queryset, NameExpr)
|
||||||
|
|
||||||
@@ -55,15 +52,14 @@ def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefConte
|
|||||||
assert isinstance(expr, StrExpr)
|
assert isinstance(expr, StrExpr)
|
||||||
custom_manager_generated_name = expr.value
|
custom_manager_generated_name = expr.value
|
||||||
else:
|
else:
|
||||||
custom_manager_generated_name = base_manager_info.name + 'From' + derived_queryset_info.name
|
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])
|
custom_manager_generated_fullname = ".".join(["django.db.models.manager", custom_manager_generated_name])
|
||||||
if 'from_queryset_managers' not in base_manager_info.metadata:
|
if "from_queryset_managers" not in base_manager_info.metadata:
|
||||||
base_manager_info.metadata['from_queryset_managers'] = {}
|
base_manager_info.metadata["from_queryset_managers"] = {}
|
||||||
base_manager_info.metadata['from_queryset_managers'][custom_manager_generated_fullname] = new_manager_info.fullname
|
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, [])
|
self_type = Instance(new_manager_info, [])
|
||||||
# we need to copy all methods in MRO before django.db.models.query.QuerySet
|
# we need to copy all methods in MRO before django.db.models.query.QuerySet
|
||||||
for class_mro_info in derived_queryset_info.mro:
|
for class_mro_info in derived_queryset_info.mro:
|
||||||
@@ -71,7 +67,6 @@ def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefConte
|
|||||||
break
|
break
|
||||||
for name, sym in class_mro_info.names.items():
|
for name, sym in class_mro_info.names.items():
|
||||||
if isinstance(sym.node, FuncDef):
|
if isinstance(sym.node, FuncDef):
|
||||||
helpers.copy_method_to_another_class(class_def_context,
|
helpers.copy_method_to_another_class(
|
||||||
self_type,
|
class_def_context, self_type, new_method_name=name, method_node=sym.node
|
||||||
new_method_name=name,
|
)
|
||||||
method_node=sym.node)
|
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ 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:
|
||||||
field_info = helpers.lookup_fully_qualified_typeinfo(helpers.get_typechecker_api(ctx),
|
field_info = helpers.lookup_fully_qualified_typeinfo(helpers.get_typechecker_api(ctx), 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 +31,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 = 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
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ 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
|
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 ARG_STAR2, Argument, Context, FuncDef, TypeInfo, Var
|
from mypy.nodes import ARG_STAR2, Argument, Context, FuncDef, TypeInfo, Var
|
||||||
from mypy.plugin import ClassDefContext
|
from mypy.plugin import ClassDefContext
|
||||||
from mypy.plugins import common
|
from mypy.plugins import common
|
||||||
@@ -35,7 +33,7 @@ class ModelClassInitializer:
|
|||||||
def lookup_typeinfo_or_incomplete_defn_error(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 helpers.IncompleteDefnException(f'No {fullname!r} found')
|
raise helpers.IncompleteDefnException(f"No {fullname!r} found")
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def lookup_class_typeinfo_or_incomplete_defn_error(self, klass: type) -> TypeInfo:
|
def lookup_class_typeinfo_or_incomplete_defn_error(self, klass: type) -> TypeInfo:
|
||||||
@@ -48,20 +46,17 @@ class ModelClassInitializer:
|
|||||||
var = Var(name=name, type=typ)
|
var = Var(name=name, type=typ)
|
||||||
# var.info: type of the object variable is bound to
|
# var.info: type of the object variable is bound to
|
||||||
var.info = self.model_classdef.info
|
var.info = self.model_classdef.info
|
||||||
var._fullname = self.model_classdef.info.fullname + '.' + 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
|
var.is_inferred = True
|
||||||
return var
|
return var
|
||||||
|
|
||||||
def add_new_node_to_model_class(self, name: str, typ: MypyType) -> None:
|
def add_new_node_to_model_class(self, name: str, typ: MypyType) -> None:
|
||||||
helpers.add_new_sym_for_info(self.model_classdef.info,
|
helpers.add_new_sym_for_info(self.model_classdef.info, name=name, sym_type=typ)
|
||||||
name=name,
|
|
||||||
sym_type=typ)
|
|
||||||
|
|
||||||
def add_new_class_for_current_module(self, name: str, bases: List[Instance]) -> TypeInfo:
|
def add_new_class_for_current_module(self, name: str, bases: List[Instance]) -> TypeInfo:
|
||||||
current_module = self.api.modules[self.model_classdef.info.module_name]
|
current_module = self.api.modules[self.model_classdef.info.module_name]
|
||||||
new_class_info = helpers.add_new_class_for_module(current_module,
|
new_class_info = helpers.add_new_class_for_module(current_module, name=name, bases=bases)
|
||||||
name=name, bases=bases)
|
|
||||||
return new_class_info
|
return new_class_info
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
@@ -103,8 +98,7 @@ class AddDefaultPrimaryKey(ModelClassInitializer):
|
|||||||
auto_field_info = self.lookup_typeinfo_or_incomplete_defn_error(auto_field_fullname)
|
auto_field_info = self.lookup_typeinfo_or_incomplete_defn_error(auto_field_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(auto_field.attname, 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):
|
||||||
@@ -117,11 +111,11 @@ class AddRelatedModelsId(ModelClassInitializer):
|
|||||||
field_sym = self.ctx.cls.info.get(field.name)
|
field_sym = self.ctx.cls.info.get(field.name)
|
||||||
if field_sym is not None and field_sym.node is not None:
|
if field_sym is not None and field_sym.node is not None:
|
||||||
error_context = field_sym.node
|
error_context = field_sym.node
|
||||||
self.api.fail(f'Cannot find model {field.related_model!r} '
|
self.api.fail(
|
||||||
f'referenced in field {field.name!r} ',
|
f"Cannot find model {field.related_model!r} " f"referenced in field {field.name!r} ",
|
||||||
ctx=error_context)
|
ctx=error_context,
|
||||||
self.add_new_node_to_model_class(field.attname,
|
)
|
||||||
AnyType(TypeOfAny.explicit))
|
self.add_new_node_to_model_class(field.attname, AnyType(TypeOfAny.explicit))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if related_model_cls._meta.abstract:
|
if related_model_cls._meta.abstract:
|
||||||
@@ -138,8 +132,7 @@ 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(field.attname,
|
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):
|
||||||
@@ -154,10 +147,9 @@ class AddManagers(ModelClassInitializer):
|
|||||||
|
|
||||||
def get_generated_manager_mappings(self, base_manager_fullname: str) -> Dict[str, str]:
|
def get_generated_manager_mappings(self, base_manager_fullname: str) -> Dict[str, str]:
|
||||||
base_manager_info = self.lookup_typeinfo(base_manager_fullname)
|
base_manager_info = self.lookup_typeinfo(base_manager_fullname)
|
||||||
if (base_manager_info is None
|
if base_manager_info is None or "from_queryset_managers" not in base_manager_info.metadata:
|
||||||
or 'from_queryset_managers' not in base_manager_info.metadata):
|
|
||||||
return {}
|
return {}
|
||||||
return base_manager_info.metadata['from_queryset_managers']
|
return base_manager_info.metadata["from_queryset_managers"]
|
||||||
|
|
||||||
def create_new_model_parametrized_manager(self, name: str, base_manager_info: TypeInfo) -> Instance:
|
def create_new_model_parametrized_manager(self, name: str, base_manager_info: TypeInfo) -> Instance:
|
||||||
bases = []
|
bases = []
|
||||||
@@ -166,31 +158,27 @@ class AddManagers(ModelClassInitializer):
|
|||||||
if original_base.type is None:
|
if original_base.type is None:
|
||||||
raise helpers.IncompleteDefnException()
|
raise helpers.IncompleteDefnException()
|
||||||
|
|
||||||
original_base = helpers.reparametrize_instance(original_base,
|
original_base = helpers.reparametrize_instance(original_base, [Instance(self.model_classdef.info, [])])
|
||||||
[Instance(self.model_classdef.info, [])])
|
|
||||||
bases.append(original_base)
|
bases.append(original_base)
|
||||||
|
|
||||||
new_manager_info = self.add_new_class_for_current_module(name, bases)
|
new_manager_info = self.add_new_class_for_current_module(name, bases)
|
||||||
# copy fields to a new manager
|
# copy fields to a new manager
|
||||||
new_cls_def_context = ClassDefContext(cls=new_manager_info.defn,
|
new_cls_def_context = ClassDefContext(cls=new_manager_info.defn, reason=self.ctx.reason, api=self.api)
|
||||||
reason=self.ctx.reason,
|
|
||||||
api=self.api)
|
|
||||||
custom_manager_type = Instance(new_manager_info, [Instance(self.model_classdef.info, [])])
|
custom_manager_type = Instance(new_manager_info, [Instance(self.model_classdef.info, [])])
|
||||||
|
|
||||||
for name, sym in base_manager_info.names.items():
|
for name, sym in base_manager_info.names.items():
|
||||||
# replace self type with new class, if copying method
|
# replace self type with new class, if copying method
|
||||||
if isinstance(sym.node, FuncDef):
|
if isinstance(sym.node, FuncDef):
|
||||||
helpers.copy_method_to_another_class(new_cls_def_context,
|
helpers.copy_method_to_another_class(
|
||||||
self_type=custom_manager_type,
|
new_cls_def_context, self_type=custom_manager_type, new_method_name=name, method_node=sym.node
|
||||||
new_method_name=name,
|
)
|
||||||
method_node=sym.node)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
new_sym = sym.copy()
|
new_sym = sym.copy()
|
||||||
if isinstance(new_sym.node, Var):
|
if isinstance(new_sym.node, Var):
|
||||||
new_var = Var(name, type=sym.type)
|
new_var = Var(name, type=sym.type)
|
||||||
new_var.info = new_manager_info
|
new_var.info = new_manager_info
|
||||||
new_var._fullname = new_manager_info.fullname + '.' + name
|
new_var._fullname = new_manager_info.fullname + "." + name
|
||||||
new_sym.node = new_var
|
new_sym.node = new_var
|
||||||
new_manager_info.names[name] = new_sym
|
new_manager_info.names[name] = new_sym
|
||||||
|
|
||||||
@@ -215,7 +203,7 @@ class AddManagers(ModelClassInitializer):
|
|||||||
manager_info = self.lookup_typeinfo(real_manager_fullname) # type: ignore
|
manager_info = self.lookup_typeinfo(real_manager_fullname) # type: ignore
|
||||||
if manager_info is None:
|
if manager_info is None:
|
||||||
continue
|
continue
|
||||||
manager_class_name = real_manager_fullname.rsplit('.', maxsplit=1)[1]
|
manager_class_name = real_manager_fullname.rsplit(".", maxsplit=1)[1]
|
||||||
|
|
||||||
if manager_name not in self.model_classdef.info.names:
|
if manager_name not in self.model_classdef.info.names:
|
||||||
manager_type = Instance(manager_info, [Instance(self.model_classdef.info, [])])
|
manager_type = Instance(manager_info, [Instance(self.model_classdef.info, [])])
|
||||||
@@ -225,10 +213,11 @@ class AddManagers(ModelClassInitializer):
|
|||||||
if not self.has_any_parametrized_manager_as_base(manager_info):
|
if not self.has_any_parametrized_manager_as_base(manager_info):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
custom_model_manager_name = manager.model.__name__ + '_' + manager_class_name
|
custom_model_manager_name = manager.model.__name__ + "_" + manager_class_name
|
||||||
try:
|
try:
|
||||||
custom_manager_type = self.create_new_model_parametrized_manager(custom_model_manager_name,
|
custom_manager_type = self.create_new_model_parametrized_manager(
|
||||||
base_manager_info=manager_info)
|
custom_model_manager_name, base_manager_info=manager_info
|
||||||
|
)
|
||||||
except helpers.IncompleteDefnException:
|
except helpers.IncompleteDefnException:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -238,11 +227,11 @@ class AddManagers(ModelClassInitializer):
|
|||||||
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:
|
||||||
# add _default_manager
|
# add _default_manager
|
||||||
if '_default_manager' not in self.model_classdef.info.names:
|
if "_default_manager" not in self.model_classdef.info.names:
|
||||||
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__)
|
||||||
default_manager_info = self.lookup_typeinfo_or_incomplete_defn_error(default_manager_fullname)
|
default_manager_info = self.lookup_typeinfo_or_incomplete_defn_error(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)
|
||||||
|
|
||||||
|
|
||||||
class AddRelatedManagers(ModelClassInitializer):
|
class AddRelatedManagers(ModelClassInitializer):
|
||||||
@@ -272,8 +261,10 @@ class AddRelatedManagers(ModelClassInitializer):
|
|||||||
|
|
||||||
if isinstance(relation, (ManyToOneRel, ManyToManyRel)):
|
if isinstance(relation, (ManyToOneRel, ManyToManyRel)):
|
||||||
try:
|
try:
|
||||||
related_manager_info = self.lookup_typeinfo_or_incomplete_defn_error(fullnames.RELATED_MANAGER_CLASS) # noqa: E501
|
related_manager_info = self.lookup_typeinfo_or_incomplete_defn_error(
|
||||||
if 'objects' not in related_model_info.names:
|
fullnames.RELATED_MANAGER_CLASS
|
||||||
|
) # noqa: E501
|
||||||
|
if "objects" not in related_model_info.names:
|
||||||
raise helpers.IncompleteDefnException()
|
raise helpers.IncompleteDefnException()
|
||||||
except helpers.IncompleteDefnException as exc:
|
except helpers.IncompleteDefnException as exc:
|
||||||
if not self.api.final_iteration:
|
if not self.api.final_iteration:
|
||||||
@@ -282,16 +273,17 @@ class AddRelatedManagers(ModelClassInitializer):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# create new RelatedManager subclass
|
# create new RelatedManager subclass
|
||||||
parametrized_related_manager_type = Instance(related_manager_info,
|
parametrized_related_manager_type = Instance(related_manager_info, [Instance(related_model_info, [])])
|
||||||
[Instance(related_model_info, [])])
|
default_manager_type = related_model_info.names["objects"].type
|
||||||
default_manager_type = related_model_info.names['objects'].type
|
if (
|
||||||
if (default_manager_type is None
|
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(attname, 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)
|
||||||
|
|
||||||
@@ -303,45 +295,50 @@ 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_incomplete_defn_error('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=f"get_{field.attname}_display", args=[], return_type=return_type)
|
||||||
name='get_{}_display'.format(field.attname),
|
|
||||||
args=[],
|
|
||||||
return_type=return_type)
|
|
||||||
|
|
||||||
# get_next_by, get_previous_by for Date, DateTime
|
# get_next_by, get_previous_by for Date, DateTime
|
||||||
for field in self.django_context.get_model_fields(model_cls):
|
for field in self.django_context.get_model_fields(model_cls):
|
||||||
if isinstance(field, (DateField, DateTimeField)) and not field.null:
|
if isinstance(field, (DateField, DateTimeField)) and not field.null:
|
||||||
return_type = Instance(self.model_classdef.info, [])
|
return_type = Instance(self.model_classdef.info, [])
|
||||||
common.add_method(self.ctx,
|
common.add_method(
|
||||||
name='get_next_by_{}'.format(field.attname),
|
self.ctx,
|
||||||
args=[Argument(Var('kwargs', AnyType(TypeOfAny.explicit)),
|
name=f"get_next_by_{field.attname}",
|
||||||
AnyType(TypeOfAny.explicit),
|
args=[
|
||||||
initializer=None,
|
Argument(
|
||||||
kind=ARG_STAR2)],
|
Var("kwargs", AnyType(TypeOfAny.explicit)),
|
||||||
return_type=return_type)
|
AnyType(TypeOfAny.explicit),
|
||||||
common.add_method(self.ctx,
|
initializer=None,
|
||||||
name='get_previous_by_{}'.format(field.attname),
|
kind=ARG_STAR2,
|
||||||
args=[Argument(Var('kwargs', AnyType(TypeOfAny.explicit)),
|
)
|
||||||
AnyType(TypeOfAny.explicit),
|
],
|
||||||
initializer=None,
|
return_type=return_type,
|
||||||
kind=ARG_STAR2)],
|
)
|
||||||
return_type=return_type)
|
common.add_method(
|
||||||
|
self.ctx,
|
||||||
|
name=f"get_previous_by_{field.attname}",
|
||||||
|
args=[
|
||||||
|
Argument(
|
||||||
|
Var("kwargs", AnyType(TypeOfAny.explicit)),
|
||||||
|
AnyType(TypeOfAny.explicit),
|
||||||
|
initializer=None,
|
||||||
|
kind=ARG_STAR2,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
return_type=return_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
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_incomplete_defn_error(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(self.model_classdef.info, [])]))
|
||||||
Instance(options_info, [
|
|
||||||
Instance(self.model_classdef.info, [])
|
|
||||||
]))
|
|
||||||
|
|
||||||
|
|
||||||
def process_model_class(ctx: ClassDefContext,
|
def process_model_class(ctx: ClassDefContext, django_context: DjangoContext) -> None:
|
||||||
django_context: DjangoContext) -> None:
|
|
||||||
initializers = [
|
initializers = [
|
||||||
InjectAnyAsBaseForNestedMeta,
|
InjectAnyAsBaseForNestedMeta,
|
||||||
AddDefaultPrimaryKey,
|
AddDefaultPrimaryKey,
|
||||||
|
|||||||
@@ -24,21 +24,24 @@ def typecheck_queryset_filter(ctx: MethodContext, django_context: DjangoContext)
|
|||||||
for lookup_kwarg, provided_type in zip(lookup_kwargs, provided_lookup_types):
|
for lookup_kwarg, provided_type in zip(lookup_kwargs, provided_lookup_types):
|
||||||
if lookup_kwarg is None:
|
if lookup_kwarg is None:
|
||||||
continue
|
continue
|
||||||
if (isinstance(provided_type, Instance)
|
if isinstance(provided_type, Instance) and provided_type.type.has_base(
|
||||||
and provided_type.type.has_base('django.db.models.expressions.Combinable')):
|
"django.db.models.expressions.Combinable"
|
||||||
|
):
|
||||||
provided_type = resolve_combinable_type(provided_type, django_context)
|
provided_type = resolve_combinable_type(provided_type, django_context)
|
||||||
|
|
||||||
lookup_type = django_context.resolve_lookup_expected_type(ctx, model_cls, lookup_kwarg)
|
lookup_type = django_context.resolve_lookup_expected_type(ctx, model_cls, lookup_kwarg)
|
||||||
# Managers as provided_type is not supported yet
|
# Managers as provided_type is not supported yet
|
||||||
if (isinstance(provided_type, Instance)
|
if isinstance(provided_type, Instance) and helpers.has_any_of_bases(
|
||||||
and helpers.has_any_of_bases(provided_type.type, (fullnames.MANAGER_CLASS_FULLNAME,
|
provided_type.type, (fullnames.MANAGER_CLASS_FULLNAME, fullnames.QUERYSET_CLASS_FULLNAME)
|
||||||
fullnames.QUERYSET_CLASS_FULLNAME))):
|
):
|
||||||
return ctx.default_return_type
|
return ctx.default_return_type
|
||||||
|
|
||||||
helpers.check_types_compatible(ctx,
|
helpers.check_types_compatible(
|
||||||
expected_type=lookup_type,
|
ctx,
|
||||||
actual_type=provided_type,
|
expected_type=lookup_type,
|
||||||
error_message=f'Incompatible type for lookup {lookup_kwarg!r}:')
|
actual_type=provided_type,
|
||||||
|
error_message=f"Incompatible type for lookup {lookup_kwarg!r}:",
|
||||||
|
)
|
||||||
|
|
||||||
return ctx.default_return_type
|
return ctx.default_return_type
|
||||||
|
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ 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 (
|
from mypy_django_plugin.django.context import DjangoContext, LookupsAreUnsupported
|
||||||
DjangoContext, LookupsAreUnsupported,
|
|
||||||
)
|
|
||||||
from mypy_django_plugin.lib import 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]:
|
||||||
for base_type in [queryset_type, *queryset_type.type.bases]:
|
for base_type in [queryset_type, *queryset_type.type.bases]:
|
||||||
if (len(base_type.args)
|
if (
|
||||||
and isinstance(base_type.args[0], Instance)
|
len(base_type.args)
|
||||||
and base_type.args[0].type.has_base(fullnames.MODEL_CLASS_FULLNAME)):
|
and isinstance(base_type.args[0], Instance)
|
||||||
|
and base_type.args[0].type.has_base(fullnames.MODEL_CLASS_FULLNAME)
|
||||||
|
):
|
||||||
return base_type.args[0]
|
return base_type.args[0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -31,15 +31,15 @@ def determine_proper_manager_type(ctx: FunctionContext) -> MypyType:
|
|||||||
assert isinstance(default_return_type, Instance)
|
assert isinstance(default_return_type, Instance)
|
||||||
|
|
||||||
outer_model_info = 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
|
||||||
|
|
||||||
return helpers.reparametrize_instance(default_return_type, [Instance(outer_model_info, [])])
|
return helpers.reparametrize_instance(default_return_type, [Instance(outer_model_info, [])])
|
||||||
|
|
||||||
|
|
||||||
def get_field_type_from_lookup(ctx: MethodContext, django_context: DjangoContext, model_cls: Type[Model],
|
def get_field_type_from_lookup(
|
||||||
*, method: str, lookup: str) -> Optional[MypyType]:
|
ctx: MethodContext, django_context: DjangoContext, model_cls: Type[Model], *, method: str, lookup: str
|
||||||
|
) -> Optional[MypyType]:
|
||||||
try:
|
try:
|
||||||
lookup_field = django_context.resolve_lookup_into_field(model_cls, lookup)
|
lookup_field = django_context.resolve_lookup_into_field(model_cls, lookup)
|
||||||
except FieldError as exc:
|
except FieldError as exc:
|
||||||
@@ -48,20 +48,21 @@ def get_field_type_from_lookup(ctx: MethodContext, django_context: DjangoContext
|
|||||||
except LookupsAreUnsupported:
|
except LookupsAreUnsupported:
|
||||||
return AnyType(TypeOfAny.explicit)
|
return AnyType(TypeOfAny.explicit)
|
||||||
|
|
||||||
if ((isinstance(lookup_field, RelatedField) and lookup_field.column == lookup)
|
if (isinstance(lookup_field, RelatedField) and lookup_field.column == lookup) or isinstance(
|
||||||
or isinstance(lookup_field, ForeignObjectRel)):
|
lookup_field, ForeignObjectRel
|
||||||
|
):
|
||||||
related_model_cls = django_context.get_field_related_model_cls(lookup_field)
|
related_model_cls = django_context.get_field_related_model_cls(lookup_field)
|
||||||
if related_model_cls is None:
|
if related_model_cls is None:
|
||||||
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(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
|
||||||
|
|
||||||
|
|
||||||
def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext, model_cls: Type[Model],
|
def get_values_list_row_type(
|
||||||
flat: bool, named: bool) -> MypyType:
|
ctx: MethodContext, django_context: DjangoContext, model_cls: Type[Model], flat: bool, named: bool
|
||||||
|
) -> MypyType:
|
||||||
field_lookups = resolve_field_lookups(ctx.args[0], django_context)
|
field_lookups = resolve_field_lookups(ctx.args[0], django_context)
|
||||||
if field_lookups is None:
|
if field_lookups is None:
|
||||||
return AnyType(TypeOfAny.from_error)
|
return AnyType(TypeOfAny.from_error)
|
||||||
@@ -70,17 +71,17 @@ def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext,
|
|||||||
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)
|
||||||
lookup_type = get_field_type_from_lookup(ctx, django_context, model_cls,
|
lookup_type = get_field_type_from_lookup(
|
||||||
lookup=primary_key_field.attname, method='values_list')
|
ctx, django_context, model_cls, lookup=primary_key_field.attname, method="values_list"
|
||||||
|
)
|
||||||
assert lookup_type is not None
|
assert lookup_type is not None
|
||||||
return lookup_type
|
return lookup_type
|
||||||
elif named:
|
elif named:
|
||||||
column_types: 'OrderedDict[str, MypyType]' = OrderedDict()
|
column_types: "OrderedDict[str, MypyType]" = OrderedDict()
|
||||||
for field in django_context.get_model_fields(model_cls):
|
for field in django_context.get_model_fields(model_cls):
|
||||||
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 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 = []
|
||||||
@@ -93,8 +94,9 @@ def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext,
|
|||||||
|
|
||||||
column_types = OrderedDict()
|
column_types = OrderedDict()
|
||||||
for field_lookup in field_lookups:
|
for field_lookup in field_lookups:
|
||||||
lookup_field_type = get_field_type_from_lookup(ctx, django_context, model_cls,
|
lookup_field_type = get_field_type_from_lookup(
|
||||||
lookup=field_lookup, method='values_list')
|
ctx, django_context, model_cls, lookup=field_lookup, method="values_list"
|
||||||
|
)
|
||||||
if lookup_field_type is None:
|
if lookup_field_type is None:
|
||||||
return AnyType(TypeOfAny.from_error)
|
return AnyType(TypeOfAny.from_error)
|
||||||
column_types[field_lookup] = lookup_field_type
|
column_types[field_lookup] = lookup_field_type
|
||||||
@@ -103,7 +105,7 @@ 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 = 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 = helpers.make_tuple(typechecker_api, list(column_types.values()))
|
row_type = helpers.make_tuple(typechecker_api, list(column_types.values()))
|
||||||
|
|
||||||
@@ -123,13 +125,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 = 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 = 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:
|
||||||
@@ -143,8 +145,7 @@ def extract_proper_type_queryset_values_list(ctx: MethodContext, django_context:
|
|||||||
flat = flat or False
|
flat = flat or False
|
||||||
named = named or False
|
named = named or False
|
||||||
|
|
||||||
row_type = get_values_list_row_type(ctx, django_context, model_cls,
|
row_type = get_values_list_row_type(ctx, django_context, model_cls, flat=flat, named=named)
|
||||||
flat=flat, named=named)
|
|
||||||
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, row_type])
|
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, row_type])
|
||||||
|
|
||||||
|
|
||||||
@@ -179,10 +180,11 @@ def extract_proper_type_queryset_values(ctx: MethodContext, django_context: Djan
|
|||||||
for field in django_context.get_model_fields(model_cls):
|
for field in django_context.get_model_fields(model_cls):
|
||||||
field_lookups.append(field.attname)
|
field_lookups.append(field.attname)
|
||||||
|
|
||||||
column_types: 'OrderedDict[str, MypyType]' = OrderedDict()
|
column_types: "OrderedDict[str, MypyType]" = OrderedDict()
|
||||||
for field_lookup in field_lookups:
|
for field_lookup in field_lookups:
|
||||||
field_lookup_type = get_field_type_from_lookup(ctx, django_context, model_cls,
|
field_lookup_type = get_field_type_from_lookup(
|
||||||
lookup=field_lookup, method='values')
|
ctx, django_context, model_cls, lookup=field_lookup, method="values"
|
||||||
|
)
|
||||||
if field_lookup_type is None:
|
if field_lookup_type is None:
|
||||||
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, AnyType(TypeOfAny.from_error)])
|
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, AnyType(TypeOfAny.from_error)])
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +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(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)
|
||||||
|
|
||||||
@@ -32,7 +31,7 @@ def get_type_of_settings_attribute(ctx: AttributeContext, django_context: Django
|
|||||||
|
|
||||||
# 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)
|
||||||
global_settings_module = typechecker_api.modules.get('django.conf.global_settings')
|
global_settings_module = typechecker_api.modules.get("django.conf.global_settings")
|
||||||
for module in [settings_module, global_settings_module]:
|
for module in [settings_module, global_settings_module]:
|
||||||
if module is not None:
|
if module is not None:
|
||||||
sym = module.names.get(setting_name)
|
sym = module.names.get(setting_name)
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 120
|
line-length = 120
|
||||||
include = 'django-stubs/.*\.pyi$'
|
include = '\.pyi?$'
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
line_length = 120
|
||||||
|
multi_line_output = 3
|
||||||
|
include_trailing_comma = true
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
[pytest]
|
[pytest]
|
||||||
testpaths =
|
testpaths =
|
||||||
./test-plugin
|
./tests
|
||||||
./test-data
|
|
||||||
addopts =
|
addopts =
|
||||||
--tb=native
|
--tb=native
|
||||||
-s
|
-s
|
||||||
-v
|
-v
|
||||||
--cache-clear
|
--cache-clear
|
||||||
--mypy-ini-file=./test-data/plugins.ini
|
--mypy-ini-file=./tests/plugins.ini
|
||||||
--mypy-extension-hook=scripts.tests_extension_hook.django_plugin_hook
|
--mypy-extension-hook=scripts.tests_extension_hook.django_plugin_hook
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/usr/local/bin/xonsh
|
|
||||||
|
|
||||||
black --line-length=120 django-stubs/
|
|
||||||
17
release.sh
Executable file
17
release.sh
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
if [[ -z $(git status -s) ]]
|
||||||
|
then
|
||||||
|
if [[ "$VIRTUAL_ENV" != "" ]]
|
||||||
|
then
|
||||||
|
pip install --upgrade setuptools wheel twine
|
||||||
|
python setup.py sdist bdist_wheel
|
||||||
|
twine upload dist/*
|
||||||
|
rm -rf dist/ build/
|
||||||
|
else
|
||||||
|
echo "this script must be executed inside an active virtual env, aborting"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "git working tree is not clean, aborting"
|
||||||
|
fi
|
||||||
11
release.xsh
11
release.xsh
@@ -1,11 +0,0 @@
|
|||||||
#!/usr/local/bin/xonsh
|
|
||||||
|
|
||||||
try:
|
|
||||||
no_uncommitted_changes = bool(!(git diff-index --quiet HEAD --))
|
|
||||||
if no_uncommitted_changes:
|
|
||||||
pip install wheel twine
|
|
||||||
python setup.py sdist bdist_wheel
|
|
||||||
twine upload dist/*
|
|
||||||
|
|
||||||
finally:
|
|
||||||
rm -rf dist/ build/
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
STUBS_ROOT = Path(__file__).parent.parent / 'django-stubs'
|
|
||||||
|
|
||||||
|
|
||||||
def build_package_name(path: str) -> str:
|
|
||||||
return '.'.join(['django'] + list(Path(path).relative_to(STUBS_ROOT).with_suffix('').parts))
|
|
||||||
|
|
||||||
|
|
||||||
packages: List[str] = []
|
|
||||||
for dirpath, dirnames, filenames in os.walk(STUBS_ROOT):
|
|
||||||
if not dirnames:
|
|
||||||
package = build_package_name(dirpath)
|
|
||||||
packages.append(package)
|
|
||||||
|
|
||||||
for filename in filenames:
|
|
||||||
if filename != '__init__.pyi':
|
|
||||||
package = build_package_name(os.path.join(dirpath, filename))
|
|
||||||
packages.append(package)
|
|
||||||
|
|
||||||
test_lines: List[str] = []
|
|
||||||
for package in packages:
|
|
||||||
test_lines.append('import ' + package)
|
|
||||||
|
|
||||||
test_contents = '\n'.join(sorted(test_lines))
|
|
||||||
print(test_contents)
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import os
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import libcst
|
|
||||||
from libcst import Annotation, BaseExpression, FunctionDef, Name, Subscript
|
|
||||||
from libcst.metadata import SyntacticPositionProvider
|
|
||||||
|
|
||||||
BASE_DIR = 'django-stubs'
|
|
||||||
|
|
||||||
fpath = os.path.join(BASE_DIR, 'core', 'checks', 'model_checks.pyi')
|
|
||||||
with open(fpath, 'r') as f:
|
|
||||||
contents = f.read()
|
|
||||||
|
|
||||||
|
|
||||||
tree = libcst.parse_module(contents)
|
|
||||||
|
|
||||||
|
|
||||||
class TypeAnnotationsAnalyzer(libcst.CSTVisitor):
|
|
||||||
METADATA_DEPENDENCIES = (SyntacticPositionProvider,)
|
|
||||||
|
|
||||||
def __init__(self, fpath: str):
|
|
||||||
super().__init__()
|
|
||||||
self.fpath = fpath
|
|
||||||
|
|
||||||
def get_node_location(self, node: FunctionDef) -> str:
|
|
||||||
start_line = self.get_metadata(SyntacticPositionProvider, node).start.line
|
|
||||||
return f'{self.fpath}:{start_line}'
|
|
||||||
|
|
||||||
def show_error_for_node(self, node: FunctionDef, error_message: str):
|
|
||||||
print(self.get_node_location(node), error_message)
|
|
||||||
|
|
||||||
def check_subscripted_annotation(self, annotation: BaseExpression) -> Optional[str]:
|
|
||||||
if isinstance(annotation, Subscript):
|
|
||||||
if isinstance(annotation.value, Name):
|
|
||||||
error_message = self.check_concrete_class_usage(annotation.value)
|
|
||||||
if error_message:
|
|
||||||
return error_message
|
|
||||||
|
|
||||||
if annotation.value.value == 'Union':
|
|
||||||
for slice_param in annotation.slice:
|
|
||||||
if isinstance(slice_param.slice.value, Name):
|
|
||||||
error_message = self.check_concrete_class_usage(annotation.value)
|
|
||||||
if error_message:
|
|
||||||
return error_message
|
|
||||||
|
|
||||||
def check_concrete_class_usage(self, name_node: Name) -> Optional[str]:
|
|
||||||
if name_node.value == 'List':
|
|
||||||
return (f'Concrete class {name_node.value!r} used for an iterable annotation. '
|
|
||||||
f'Use abstract collection (Iterable, Collection, Sequence) instead')
|
|
||||||
|
|
||||||
def visit_FunctionDef(self, node: FunctionDef) -> Optional[bool]:
|
|
||||||
params_node = node.params
|
|
||||||
for param_node in [*params_node.params, *params_node.default_params]:
|
|
||||||
param_name = param_node.name.value
|
|
||||||
annotation_node = param_node.annotation # type: Annotation
|
|
||||||
if annotation_node is not None:
|
|
||||||
annotation = annotation_node.annotation
|
|
||||||
if annotation.value == 'None':
|
|
||||||
self.show_error_for_node(node, f'"None" type annotation used for parameter {param_name!r}')
|
|
||||||
continue
|
|
||||||
|
|
||||||
error_message = self.check_subscripted_annotation(annotation)
|
|
||||||
if error_message is not None:
|
|
||||||
self.show_error_for_node(node, error_message)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if node.returns is not None:
|
|
||||||
return_annotation = node.returns.annotation
|
|
||||||
if isinstance(return_annotation, Subscript) and return_annotation.value.value == 'Union':
|
|
||||||
self.show_error_for_node(node, 'Union is return type annotation')
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
for dirpath, dirnames, filenames in os.walk(BASE_DIR):
|
|
||||||
for filename in filenames:
|
|
||||||
fpath = os.path.join(dirpath, filename)
|
|
||||||
# skip all other checks for now, low priority
|
|
||||||
if not fpath.startswith(('django-stubs/db', 'django-stubs/views', 'django-stubs/apps',
|
|
||||||
'django-stubs/http', 'django-stubs/contrib/postgres')):
|
|
||||||
continue
|
|
||||||
|
|
||||||
with open(fpath, 'r') as f:
|
|
||||||
contents = f.read()
|
|
||||||
|
|
||||||
tree = libcst.MetadataWrapper(libcst.parse_module(contents))
|
|
||||||
analyzer = TypeAnnotationsAnalyzer(fpath)
|
|
||||||
tree.visit(analyzer)
|
|
||||||
@@ -1,233 +1,237 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
import django
|
import django
|
||||||
|
|
||||||
SECRET_KEY = '1'
|
SECRET_KEY = "1"
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'django.contrib.contenttypes',
|
"django.contrib.contenttypes",
|
||||||
'django.contrib.auth',
|
"django.contrib.auth",
|
||||||
'django.contrib.sites',
|
"django.contrib.sites",
|
||||||
'django.contrib.sessions',
|
"django.contrib.sessions",
|
||||||
'django.contrib.messages',
|
"django.contrib.messages",
|
||||||
'django.contrib.admin.apps.SimpleAdminConfig',
|
"django.contrib.admin.apps.SimpleAdminConfig",
|
||||||
'django.contrib.staticfiles',
|
"django.contrib.staticfiles",
|
||||||
]
|
]
|
||||||
|
|
||||||
test_modules = [
|
test_modules = [
|
||||||
'absolute_url_overrides',
|
"absolute_url_overrides",
|
||||||
'admin_autodiscover',
|
"admin_autodiscover",
|
||||||
'admin_changelist',
|
"admin_changelist",
|
||||||
'admin_checks',
|
"admin_checks",
|
||||||
'admin_custom_urls',
|
"admin_custom_urls",
|
||||||
'admin_default_site',
|
"admin_default_site",
|
||||||
'admin_docs',
|
"admin_docs",
|
||||||
'admin_filters',
|
"admin_filters",
|
||||||
'admin_inlines',
|
"admin_inlines",
|
||||||
'admin_ordering',
|
"admin_ordering",
|
||||||
'admin_registration',
|
"admin_registration",
|
||||||
'admin_scripts',
|
"admin_scripts",
|
||||||
'admin_utils',
|
"admin_utils",
|
||||||
'admin_views',
|
"admin_views",
|
||||||
'admin_widgets',
|
"admin_widgets",
|
||||||
'aggregation',
|
"aggregation",
|
||||||
'aggregation_regress',
|
"aggregation_regress",
|
||||||
'annotations',
|
"annotations",
|
||||||
'app_loading',
|
"app_loading",
|
||||||
'apps',
|
"apps",
|
||||||
'auth_tests',
|
"auth_tests",
|
||||||
'backends',
|
"backends",
|
||||||
'base',
|
"base",
|
||||||
'bash_completion',
|
"bash_completion",
|
||||||
'basic',
|
"basic",
|
||||||
'builtin_server',
|
"builtin_server",
|
||||||
'bulk_create',
|
"bulk_create",
|
||||||
'cache',
|
"cache",
|
||||||
'check_framework',
|
"check_framework",
|
||||||
'conditional_processing',
|
"conditional_processing",
|
||||||
'constraints',
|
"constraints",
|
||||||
'contenttypes_tests',
|
"contenttypes_tests",
|
||||||
'context_processors',
|
"context_processors",
|
||||||
'csrf_tests',
|
"csrf_tests",
|
||||||
'custom_columns',
|
"custom_columns",
|
||||||
'custom_lookups',
|
"custom_lookups",
|
||||||
'custom_managers',
|
"custom_managers",
|
||||||
'custom_methods',
|
"custom_methods",
|
||||||
'custom_migration_operations',
|
"custom_migration_operations",
|
||||||
'custom_pk',
|
"custom_pk",
|
||||||
'datatypes',
|
"datatypes",
|
||||||
'dates',
|
"dates",
|
||||||
'datetimes',
|
"datetimes",
|
||||||
'db_functions',
|
"db_functions",
|
||||||
'db_typecasts',
|
"db_typecasts",
|
||||||
'db_utils',
|
"db_utils",
|
||||||
'dbshell',
|
"dbshell",
|
||||||
'decorators',
|
"decorators",
|
||||||
'defer',
|
"defer",
|
||||||
'defer_regress',
|
"defer_regress",
|
||||||
'delete',
|
"delete",
|
||||||
'delete_regress',
|
"delete_regress",
|
||||||
'deprecation',
|
"deprecation",
|
||||||
'dispatch',
|
"dispatch",
|
||||||
'distinct_on_fields',
|
"distinct_on_fields",
|
||||||
'empty',
|
"empty",
|
||||||
'expressions',
|
"expressions",
|
||||||
'expressions_case',
|
"expressions_case",
|
||||||
'expressions_window',
|
"expressions_window",
|
||||||
'extra_regress',
|
"extra_regress",
|
||||||
'field_deconstruction',
|
"field_deconstruction",
|
||||||
'field_defaults',
|
"field_defaults",
|
||||||
'field_subclassing',
|
"field_subclassing",
|
||||||
'file_storage',
|
"file_storage",
|
||||||
'file_uploads',
|
"file_uploads",
|
||||||
'files',
|
"files",
|
||||||
'filtered_relation',
|
"filtered_relation",
|
||||||
'fixtures',
|
"fixtures",
|
||||||
'fixtures_model_package',
|
"fixtures_model_package",
|
||||||
'fixtures_regress',
|
"fixtures_regress",
|
||||||
'flatpages_tests',
|
"flatpages_tests",
|
||||||
'force_insert_update',
|
"force_insert_update",
|
||||||
'foreign_object',
|
"foreign_object",
|
||||||
'forms_tests',
|
"forms_tests",
|
||||||
'from_db_value',
|
"from_db_value",
|
||||||
'generic_inline_admin',
|
"generic_inline_admin",
|
||||||
'generic_relations',
|
"generic_relations",
|
||||||
'generic_relations_regress',
|
"generic_relations_regress",
|
||||||
'generic_views',
|
"generic_views",
|
||||||
'get_earliest_or_latest',
|
"get_earliest_or_latest",
|
||||||
'get_object_or_404',
|
"get_object_or_404",
|
||||||
'get_or_create',
|
"get_or_create",
|
||||||
'gis_tests',
|
"gis_tests",
|
||||||
'handlers',
|
"handlers",
|
||||||
'httpwrappers',
|
"httpwrappers",
|
||||||
'humanize_tests',
|
"humanize_tests",
|
||||||
'i18n',
|
"i18n",
|
||||||
'import_error_package',
|
"import_error_package",
|
||||||
'indexes',
|
"indexes",
|
||||||
'inline_formsets',
|
"inline_formsets",
|
||||||
'inspectdb',
|
"inspectdb",
|
||||||
'introspection',
|
"introspection",
|
||||||
'invalid_models_tests',
|
"invalid_models_tests",
|
||||||
'known_related_objects',
|
"known_related_objects",
|
||||||
'logging_tests',
|
"logging_tests",
|
||||||
'lookup',
|
"lookup",
|
||||||
'm2m_and_m2o',
|
"m2m_and_m2o",
|
||||||
'm2m_intermediary',
|
"m2m_intermediary",
|
||||||
'm2m_multiple',
|
"m2m_multiple",
|
||||||
'm2m_recursive',
|
"m2m_recursive",
|
||||||
'm2m_regress',
|
"m2m_regress",
|
||||||
'm2m_signals',
|
"m2m_signals",
|
||||||
'm2m_through',
|
"m2m_through",
|
||||||
'm2m_through_regress',
|
"m2m_through_regress",
|
||||||
'm2o_recursive',
|
"m2o_recursive",
|
||||||
'mail',
|
"mail",
|
||||||
'managers_regress',
|
"managers_regress",
|
||||||
'many_to_many',
|
"many_to_many",
|
||||||
'many_to_one',
|
"many_to_one",
|
||||||
'many_to_one_null',
|
"many_to_one_null",
|
||||||
'max_lengths',
|
"max_lengths",
|
||||||
'messages_tests',
|
"messages_tests",
|
||||||
'middleware',
|
"middleware",
|
||||||
'middleware_exceptions',
|
"middleware_exceptions",
|
||||||
'migrate_signals',
|
"migrate_signals",
|
||||||
'migration_test_data_persistence',
|
"migration_test_data_persistence",
|
||||||
'migrations',
|
"migrations",
|
||||||
'migrations2',
|
"migrations2",
|
||||||
'model_fields',
|
"model_fields",
|
||||||
'model_forms',
|
"model_forms",
|
||||||
'model_formsets',
|
"model_formsets",
|
||||||
'model_formsets_regress',
|
"model_formsets_regress",
|
||||||
'model_indexes',
|
"model_indexes",
|
||||||
'model_inheritance',
|
"model_inheritance",
|
||||||
'model_inheritance_regress',
|
"model_inheritance_regress",
|
||||||
'model_meta',
|
"model_meta",
|
||||||
'model_options',
|
"model_options",
|
||||||
'model_package',
|
"model_package",
|
||||||
'model_regress',
|
"model_regress",
|
||||||
'modeladmin',
|
"modeladmin",
|
||||||
'multiple_database',
|
"multiple_database",
|
||||||
'mutually_referential',
|
"mutually_referential",
|
||||||
'nested_foreign_keys',
|
"nested_foreign_keys",
|
||||||
'no_models',
|
"no_models",
|
||||||
'null_fk',
|
"null_fk",
|
||||||
'null_fk_ordering',
|
"null_fk_ordering",
|
||||||
'null_queries',
|
"null_queries",
|
||||||
'one_to_one',
|
"one_to_one",
|
||||||
'or_lookups',
|
"or_lookups",
|
||||||
'order_with_respect_to',
|
"order_with_respect_to",
|
||||||
'ordering',
|
"ordering",
|
||||||
'pagination',
|
"pagination",
|
||||||
'postgres_tests',
|
"postgres_tests",
|
||||||
'prefetch_related',
|
"prefetch_related",
|
||||||
'project_template',
|
"project_template",
|
||||||
'properties',
|
"properties",
|
||||||
'proxy_model_inheritance',
|
"proxy_model_inheritance",
|
||||||
'proxy_models',
|
"proxy_models",
|
||||||
'queries',
|
"queries",
|
||||||
'queryset_pickle',
|
"queryset_pickle",
|
||||||
'raw_query',
|
"raw_query",
|
||||||
'redirects_tests',
|
"redirects_tests",
|
||||||
'requests',
|
"requests",
|
||||||
'reserved_names',
|
"reserved_names",
|
||||||
'resolve_url',
|
"resolve_url",
|
||||||
'responses',
|
"responses",
|
||||||
'reverse_lookup',
|
"reverse_lookup",
|
||||||
'save_delete_hooks',
|
"save_delete_hooks",
|
||||||
'schema',
|
"schema",
|
||||||
'select_for_update',
|
"select_for_update",
|
||||||
'select_related',
|
"select_related",
|
||||||
'select_related_onetoone',
|
"select_related_onetoone",
|
||||||
'select_related_regress',
|
"select_related_regress",
|
||||||
'serializers',
|
"serializers",
|
||||||
'servers',
|
"servers",
|
||||||
'sessions_tests',
|
"sessions_tests",
|
||||||
'settings_tests',
|
"settings_tests",
|
||||||
'shell',
|
"shell",
|
||||||
'shortcuts',
|
"shortcuts",
|
||||||
'signals',
|
"signals",
|
||||||
'signed_cookies_tests',
|
"signed_cookies_tests",
|
||||||
'signing',
|
"signing",
|
||||||
'sitemaps_tests',
|
"sitemaps_tests",
|
||||||
'sites_framework',
|
"sites_framework",
|
||||||
'sites_tests',
|
"sites_tests",
|
||||||
'staticfiles_tests',
|
"staticfiles_tests",
|
||||||
'str',
|
"str",
|
||||||
'string_lookup',
|
"string_lookup",
|
||||||
'swappable_models',
|
"swappable_models",
|
||||||
'syndication_tests',
|
"syndication_tests",
|
||||||
'template_backends',
|
"template_backends",
|
||||||
'template_loader',
|
"template_loader",
|
||||||
'template_tests',
|
"template_tests",
|
||||||
'test_client',
|
"test_client",
|
||||||
'test_client_regress',
|
"test_client_regress",
|
||||||
'test_exceptions',
|
"test_exceptions",
|
||||||
'test_runner',
|
"test_runner",
|
||||||
'test_runner_apps',
|
"test_runner_apps",
|
||||||
'test_utils',
|
"test_utils",
|
||||||
'timezones',
|
"timezones",
|
||||||
'transaction_hooks',
|
"transaction_hooks",
|
||||||
'transactions',
|
"transactions",
|
||||||
'unmanaged_models',
|
"unmanaged_models",
|
||||||
'update',
|
"update",
|
||||||
'update_only_fields',
|
"update_only_fields",
|
||||||
'urlpatterns',
|
"urlpatterns",
|
||||||
'urlpatterns_reverse',
|
"urlpatterns_reverse",
|
||||||
'user_commands',
|
"user_commands",
|
||||||
'utils_tests',
|
"utils_tests",
|
||||||
'validation',
|
"validation",
|
||||||
'validators',
|
"validators",
|
||||||
'version',
|
"version",
|
||||||
'view_tests',
|
"view_tests",
|
||||||
'wsgi',
|
"wsgi",
|
||||||
]
|
]
|
||||||
|
|
||||||
if django.VERSION[0] == 2:
|
if django.VERSION[0] == 2:
|
||||||
test_modules += ['choices']
|
test_modules += ["choices"]
|
||||||
|
|
||||||
invalid_apps = {
|
invalid_apps = {
|
||||||
'import_error_package',
|
"import_error_package",
|
||||||
}
|
}
|
||||||
|
|
||||||
for app in invalid_apps:
|
for app in invalid_apps:
|
||||||
test_modules.remove(app)
|
test_modules.remove(app)
|
||||||
|
|
||||||
INSTALLED_APPS += test_modules
|
|
||||||
|
if os.environ.get("TYPECHECK_TESTS"):
|
||||||
|
INSTALLED_APPS += test_modules
|
||||||
|
|||||||
@@ -2,27 +2,69 @@
|
|||||||
# using this constant.
|
# using this constant.
|
||||||
import re
|
import re
|
||||||
|
|
||||||
IGNORED_MODULES = {'schema', 'gis_tests', 'admin_widgets', 'admin_filters',
|
IGNORED_MODULES = {
|
||||||
'sitemaps_tests', 'staticfiles_tests', 'modeladmin',
|
"schema",
|
||||||
'generic_views', 'forms_tests', 'flatpages_tests',
|
"gis_tests",
|
||||||
'admin_ordering', 'admin_changelist', 'admin_views',
|
"admin_widgets",
|
||||||
'invalid_models_tests', 'i18n', 'model_formsets',
|
"admin_filters",
|
||||||
'template_tests', 'template_backends', 'test_runner', 'admin_scripts',
|
"sitemaps_tests",
|
||||||
'inline_formsets', 'foreign_object', 'cache'}
|
"staticfiles_tests",
|
||||||
|
"modeladmin",
|
||||||
|
"generic_views",
|
||||||
|
"forms_tests",
|
||||||
|
"flatpages_tests",
|
||||||
|
"admin_ordering",
|
||||||
|
"admin_changelist",
|
||||||
|
"admin_views",
|
||||||
|
"invalid_models_tests",
|
||||||
|
"i18n",
|
||||||
|
"model_formsets",
|
||||||
|
"template_tests",
|
||||||
|
"template_backends",
|
||||||
|
"test_runner",
|
||||||
|
"admin_scripts",
|
||||||
|
"inline_formsets",
|
||||||
|
"foreign_object",
|
||||||
|
"cache",
|
||||||
|
}
|
||||||
|
|
||||||
MOCK_OBJECTS = ['MockRequest', 'MockCompiler', 'MockModelAdmin', 'modelz', 'call_count', 'call_args_list',
|
MOCK_OBJECTS = [
|
||||||
'call_args', 'MockUser', 'Xtemplate', 'DummyRequest', 'DummyUser', 'MinimalUser', 'DummyNode']
|
"MockRequest",
|
||||||
EXTERNAL_MODULES = ['psycopg2', 'PIL', 'selenium', 'oracle', 'mysql', 'sqlparse', 'tblib', 'numpy',
|
"MockCompiler",
|
||||||
'bcrypt', 'argon2', 'xml.dom']
|
"MockModelAdmin",
|
||||||
|
"modelz",
|
||||||
|
"call_count",
|
||||||
|
"call_args_list",
|
||||||
|
"call_args",
|
||||||
|
"MockUser",
|
||||||
|
"Xtemplate",
|
||||||
|
"DummyRequest",
|
||||||
|
"DummyUser",
|
||||||
|
"MinimalUser",
|
||||||
|
"DummyNode",
|
||||||
|
]
|
||||||
|
EXTERNAL_MODULES = [
|
||||||
|
"psycopg2",
|
||||||
|
"PIL",
|
||||||
|
"selenium",
|
||||||
|
"oracle",
|
||||||
|
"mysql",
|
||||||
|
"sqlparse",
|
||||||
|
"tblib",
|
||||||
|
"numpy",
|
||||||
|
"bcrypt",
|
||||||
|
"argon2",
|
||||||
|
"xml.dom",
|
||||||
|
]
|
||||||
IGNORED_ERRORS = {
|
IGNORED_ERRORS = {
|
||||||
'__common__': [
|
"__common__": [
|
||||||
*MOCK_OBJECTS,
|
*MOCK_OBJECTS,
|
||||||
*EXTERNAL_MODULES,
|
*EXTERNAL_MODULES,
|
||||||
'Need type annotation for',
|
"Need type annotation for",
|
||||||
'has no attribute "getvalue"',
|
'has no attribute "getvalue"',
|
||||||
'Cannot assign to a method',
|
"Cannot assign to a method",
|
||||||
'already defined',
|
"already defined",
|
||||||
'Cannot assign to a type',
|
"Cannot assign to a type",
|
||||||
'"HttpResponseBase" has no attribute',
|
'"HttpResponseBase" has no attribute',
|
||||||
'"object" has no attribute',
|
'"object" has no attribute',
|
||||||
re.compile(r'"Callable\[.+, Any\]" has no attribute'),
|
re.compile(r'"Callable\[.+, Any\]" has no attribute'),
|
||||||
@@ -30,259 +72,245 @@ IGNORED_ERRORS = {
|
|||||||
# private members
|
# private members
|
||||||
re.compile(r'has no attribute ("|\')_[a-zA-Z_]+("|\')'),
|
re.compile(r'has no attribute ("|\')_[a-zA-Z_]+("|\')'),
|
||||||
"'Settings' object has no attribute",
|
"'Settings' object has no attribute",
|
||||||
'**Dict',
|
"**Dict",
|
||||||
'has incompatible type "object"',
|
'has incompatible type "object"',
|
||||||
'undefined in superclass',
|
"undefined in superclass",
|
||||||
'Argument after ** must be a mapping',
|
"Argument after ** must be a mapping",
|
||||||
'note:',
|
"note:",
|
||||||
re.compile(r'Item "None" of "[a-zA-Z_ ,\[\]]+" has no attribute'),
|
re.compile(r'Item "None" of "[a-zA-Z_ ,\[\]]+" has no attribute'),
|
||||||
'"Callable[..., None]" has no attribute',
|
'"Callable[..., None]" has no attribute',
|
||||||
'does not return a value',
|
"does not return a value",
|
||||||
'has no attribute "alternatives"',
|
'has no attribute "alternatives"',
|
||||||
'gets multiple values for keyword argument',
|
"gets multiple values for keyword argument",
|
||||||
'"Handler" has no attribute',
|
'"Handler" has no attribute',
|
||||||
'Module has no attribute',
|
"Module has no attribute",
|
||||||
'namedtuple',
|
"namedtuple",
|
||||||
# TODO: see test in managers/test_managers.yml
|
# TODO: see test in managers/test_managers.yml
|
||||||
"Cannot determine type of",
|
"Cannot determine type of",
|
||||||
'cache_clear',
|
"cache_clear",
|
||||||
'cache_info',
|
"cache_info",
|
||||||
'Incompatible types in assignment (expression has type "None", variable has type Module)',
|
'Incompatible types in assignment (expression has type "None", variable has type Module)',
|
||||||
"Module 'django.contrib.messages.storage.fallback' has no attribute 'CookieStorage'",
|
"Module 'django.contrib.messages.storage.fallback' has no attribute 'CookieStorage'",
|
||||||
# TODO: not supported yet
|
# TODO: not supported yet
|
||||||
'GenericRelation',
|
"GenericRelation",
|
||||||
'RelatedObjectDoesNotExist',
|
"RelatedObjectDoesNotExist",
|
||||||
re.compile(r'"Field\[Any, Any\]" has no attribute '
|
re.compile(
|
||||||
r'"(through|field_name|field|get_related_field|related_model|related_name'
|
r'"Field\[Any, Any\]" has no attribute '
|
||||||
r'|get_accessor_name|empty_strings_allowed|many_to_many)"'),
|
r'"(through|field_name|field|get_related_field|related_model|related_name'
|
||||||
|
r'|get_accessor_name|empty_strings_allowed|many_to_many)"'
|
||||||
|
),
|
||||||
# TODO: multitable inheritance
|
# TODO: multitable inheritance
|
||||||
'ptr',
|
"ptr",
|
||||||
'Incompatible types in assignment (expression has type "Callable[',
|
'Incompatible types in assignment (expression has type "Callable[',
|
||||||
'SimpleLazyObject',
|
"SimpleLazyObject",
|
||||||
'Test',
|
"Test",
|
||||||
'Mixin" has no attribute',
|
'Mixin" has no attribute',
|
||||||
'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 "streaming_content"',
|
||||||
'error: "HttpResponse" has no attribute "context_data"',
|
'error: "HttpResponse" has no attribute "context_data"',
|
||||||
],
|
],
|
||||||
'admin_checks': [
|
"admin_checks": ['Argument 1 to "append" of "list" has incompatible type "str"; expected "CheckMessage"'],
|
||||||
'Argument 1 to "append" of "list" has incompatible type "str"; expected "CheckMessage"'
|
"admin_inlines": [
|
||||||
],
|
|
||||||
'admin_inlines': [
|
|
||||||
'error: "HttpResponse" has no attribute "rendered_content"',
|
'error: "HttpResponse" has no attribute "rendered_content"',
|
||||||
],
|
],
|
||||||
'admin_utils': [
|
"admin_utils": [
|
||||||
'"Article" has no attribute "non_field"',
|
'"Article" has no attribute "non_field"',
|
||||||
],
|
],
|
||||||
'aggregation': [
|
"aggregation": [
|
||||||
re.compile(r'got "Optional\[(Author|Publisher)\]", expected "Union\[(Author|Publisher), Combinable\]"'),
|
re.compile(r'got "Optional\[(Author|Publisher)\]", expected "Union\[(Author|Publisher), Combinable\]"'),
|
||||||
'Argument 2 for "super" not an instance of argument 1',
|
'Argument 2 for "super" not an instance of argument 1',
|
||||||
],
|
],
|
||||||
'annotations': [
|
"annotations": [
|
||||||
'Incompatible type for "store" of "Employee" (got "Optional[Store]", expected "Union[Store, Combinable]")'
|
'Incompatible type for "store" of "Employee" (got "Optional[Store]", expected "Union[Store, Combinable]")'
|
||||||
],
|
],
|
||||||
'apps': [
|
"apps": [
|
||||||
'Incompatible types in assignment (expression has type "str", target has type "type")',
|
'Incompatible types in assignment (expression has type "str", target has type "type")',
|
||||||
],
|
],
|
||||||
'auth_tests': [
|
"auth_tests": [
|
||||||
'"PasswordValidator" has no attribute "min_length"',
|
'"PasswordValidator" has no attribute "min_length"',
|
||||||
'AbstractBaseUser',
|
"AbstractBaseUser",
|
||||||
'Argument "password_validators" to "password_changed" has incompatible type "Tuple[Validator]"; '
|
'Argument "password_validators" to "password_changed" has incompatible type "Tuple[Validator]"; '
|
||||||
+ 'expected "Optional[Sequence[PasswordValidator]]"',
|
+ 'expected "Optional[Sequence[PasswordValidator]]"',
|
||||||
'Unsupported right operand type for in ("object")',
|
'Unsupported right operand type for in ("object")',
|
||||||
'mock_getpass',
|
"mock_getpass",
|
||||||
'Unsupported left operand type for + ("Sequence[str]")',
|
'Unsupported left operand type for + ("Sequence[str]")',
|
||||||
'AuthenticationFormWithInactiveUsersOkay',
|
"AuthenticationFormWithInactiveUsersOkay",
|
||||||
'Incompatible types in assignment (expression has type "Dict[str, Any]", variable has type "QueryDict")',
|
'Incompatible types in assignment (expression has type "Dict[str, Any]", variable has type "QueryDict")',
|
||||||
'No overload variant of "int" matches argument type "AnonymousUser"',
|
'No overload variant of "int" matches argument type "AnonymousUser"',
|
||||||
'expression has type "AnonymousUser", variable has type "User"',
|
'expression has type "AnonymousUser", variable has type "User"',
|
||||||
],
|
],
|
||||||
'basic': [
|
"basic": [
|
||||||
'Unexpected keyword argument "unknown_kwarg" for "refresh_from_db" of "Model"',
|
'Unexpected keyword argument "unknown_kwarg" for "refresh_from_db" of "Model"',
|
||||||
'Unexpected attribute "foo" for model "Article"',
|
'Unexpected attribute "foo" for model "Article"',
|
||||||
'has no attribute "touched"',
|
'has no attribute "touched"',
|
||||||
'Incompatible types in assignment (expression has type "Type[CustomQuerySet]"',
|
'Incompatible types in assignment (expression has type "Type[CustomQuerySet]"',
|
||||||
'"Manager[Article]" has no attribute "do_something"',
|
'"Manager[Article]" has no attribute "do_something"',
|
||||||
],
|
],
|
||||||
'backends': [
|
"backends": ['"DatabaseError" has no attribute "pgcode"'],
|
||||||
'"DatabaseError" has no attribute "pgcode"'
|
"builtin_server": [
|
||||||
],
|
|
||||||
'builtin_server': [
|
|
||||||
'"ServerHandler" has no attribute',
|
'"ServerHandler" has no attribute',
|
||||||
'Incompatible types in assignment (expression has type "Tuple[BytesIO, BytesIO]"',
|
'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]"',
|
||||||
'List item 1 has incompatible type "Country"; expected "ProxyCountry"',
|
'List item 1 has incompatible type "Country"; expected "ProxyCountry"',
|
||||||
],
|
],
|
||||||
'check_framework': [
|
"check_framework": [
|
||||||
'base class "Model" defined the type as "Callable',
|
'base class "Model" defined the type as "Callable',
|
||||||
'Value of type "Collection[str]" is not indexable',
|
'Value of type "Collection[str]" is not indexable',
|
||||||
'Unsupported target for indexed assignment',
|
"Unsupported target for indexed assignment",
|
||||||
],
|
],
|
||||||
'constraints': [
|
"constraints": ['Argument "condition" to "UniqueConstraint" has incompatible type "str"; expected "Optional[Q]"'],
|
||||||
'Argument "condition" to "UniqueConstraint" has incompatible type "str"; expected "Optional[Q]"'
|
"contenttypes_tests": [
|
||||||
],
|
|
||||||
'contenttypes_tests': [
|
|
||||||
'"FooWithBrokenAbsoluteUrl" has no attribute "unknown_field"',
|
'"FooWithBrokenAbsoluteUrl" has no attribute "unknown_field"',
|
||||||
'contenttypes_tests.models.Site',
|
"contenttypes_tests.models.Site",
|
||||||
'Argument 1 to "set" of "RelatedManager" has incompatible type "SiteManager[Site]"',
|
'Argument 1 to "set" of "RelatedManager" has incompatible type "SiteManager[Site]"',
|
||||||
],
|
],
|
||||||
'custom_lookups': [
|
"custom_lookups": [
|
||||||
'in base class "SQLFuncMixin"',
|
'in base class "SQLFuncMixin"',
|
||||||
'has no attribute "name"',
|
'has no attribute "name"',
|
||||||
],
|
],
|
||||||
'custom_columns': [
|
"custom_columns": [
|
||||||
"Cannot resolve keyword 'firstname' into field",
|
"Cannot resolve keyword 'firstname' into field",
|
||||||
],
|
],
|
||||||
'custom_pk': [
|
"custom_pk": [
|
||||||
'"Employee" has no attribute "id"',
|
'"Employee" has no attribute "id"',
|
||||||
],
|
],
|
||||||
'custom_managers': [
|
"custom_managers": [
|
||||||
'Incompatible types in assignment (expression has type "CharField',
|
'Incompatible types in assignment (expression has type "CharField',
|
||||||
'Item "Book" of "Optional[Book]" has no attribute "favorite_avg"',
|
'Item "Book" of "Optional[Book]" has no attribute "favorite_avg"',
|
||||||
],
|
],
|
||||||
'csrf_tests': [
|
"csrf_tests": [
|
||||||
'expression has type "property", base class "HttpRequest" defined the type as "QueryDict"',
|
'expression has type "property", base class "HttpRequest" defined the type as "QueryDict"',
|
||||||
'expression has type "Dict[<nothing>, <nothing>]", variable has type "SessionBase"',
|
'expression has type "Dict[<nothing>, <nothing>]", variable has type "SessionBase"',
|
||||||
],
|
],
|
||||||
'dates': [
|
"dates": [
|
||||||
'Too few arguments for "dates" of',
|
'Too few arguments for "dates" of',
|
||||||
],
|
],
|
||||||
'dbshell': [
|
"dbshell": [
|
||||||
'Incompatible types in assignment (expression has type "None"',
|
'Incompatible types in assignment (expression has type "None"',
|
||||||
],
|
],
|
||||||
'defer': [
|
"defer": ['Too many arguments for "refresh_from_db" of "Model"'],
|
||||||
'Too many arguments for "refresh_from_db" of "Model"'
|
"delete": [
|
||||||
],
|
|
||||||
'delete': [
|
|
||||||
'Incompatible type for lookup \'pk\': (got "Optional[int]", expected "Union[str, int]")',
|
'Incompatible type for lookup \'pk\': (got "Optional[int]", expected "Union[str, int]")',
|
||||||
],
|
],
|
||||||
'dispatch': [
|
"dispatch": ['Item "str" of "Union[ValueError, str]" has no attribute "args"'],
|
||||||
'Item "str" of "Union[ValueError, str]" has no attribute "args"'
|
"deprecation": ['"Manager" has no attribute "old"', '"Manager" has no attribute "new"'],
|
||||||
],
|
"db_functions": [
|
||||||
'deprecation': [
|
|
||||||
'"Manager" has no attribute "old"',
|
|
||||||
'"Manager" has no attribute "new"'
|
|
||||||
],
|
|
||||||
'db_functions': [
|
|
||||||
'"FloatModel" has no attribute',
|
'"FloatModel" has no attribute',
|
||||||
'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"'
|
'Value of type variable "_T" of function cannot be "descriptor_wrapper"',
|
||||||
],
|
],
|
||||||
'expressions_window': [
|
"expressions_window": ['has incompatible type "str"'],
|
||||||
'has incompatible type "str"'
|
"file_uploads": [
|
||||||
],
|
|
||||||
'file_uploads': [
|
|
||||||
'has no attribute "content_type"',
|
'has no attribute "content_type"',
|
||||||
],
|
],
|
||||||
'file_storage': [
|
"file_storage": [
|
||||||
'Incompatible types in assignment (expression has type "None", variable has type "str")',
|
'Incompatible types in assignment (expression has type "None", variable has type "str")',
|
||||||
'Property "base_url" defined in "FileSystemStorage" is read-only',
|
'Property "base_url" defined in "FileSystemStorage" is read-only',
|
||||||
],
|
],
|
||||||
'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"',
|
'Argument 1 to "TextIOWrapper" has incompatible type "File"; expected "BinaryIO"',
|
||||||
'Incompatible types in assignment (expression has type "BinaryIO", variable has type "File")',
|
'Incompatible types in assignment (expression has type "BinaryIO", variable has type "File")',
|
||||||
],
|
],
|
||||||
'filtered_relation': [
|
"filtered_relation": [
|
||||||
'has no attribute "name"',
|
'has no attribute "name"',
|
||||||
],
|
],
|
||||||
'fixtures': [
|
"fixtures": [
|
||||||
'Incompatible types in assignment (expression has type "int", target has type "Iterable[str]")',
|
'Incompatible types in assignment (expression has type "int", target has type "Iterable[str]")',
|
||||||
'Incompatible types in assignment (expression has type "SpyManager[Spy]"',
|
'Incompatible types in assignment (expression has type "SpyManager[Spy]"',
|
||||||
],
|
],
|
||||||
'fixtures_regress': [
|
"fixtures_regress": [
|
||||||
'Unsupported left operand type for + ("None")',
|
'Unsupported left operand type for + ("None")',
|
||||||
],
|
],
|
||||||
'from_db_value': [
|
"from_db_value": [
|
||||||
'"Cash" has no attribute',
|
'"Cash" has no attribute',
|
||||||
'"__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"; '
|
||||||
+ 'expected "Union[Type[<nothing>], QuerySet[<nothing>]]"',
|
+ 'expected "Union[Type[<nothing>], QuerySet[<nothing>]]"',
|
||||||
'Argument 1 to "get_list_or_404" has incompatible type "List[Type[Article]]"; '
|
'Argument 1 to "get_list_or_404" has incompatible type "List[Type[Article]]"; '
|
||||||
+ 'expected "Union[Type[<nothing>], QuerySet[<nothing>]]"',
|
+ 'expected "Union[Type[<nothing>], QuerySet[<nothing>]]"',
|
||||||
'CustomClass',
|
"CustomClass",
|
||||||
],
|
],
|
||||||
'generic_relations': [
|
"generic_relations": [
|
||||||
"Cannot resolve keyword 'vegetable' into field",
|
"Cannot resolve keyword 'vegetable' into field",
|
||||||
],
|
],
|
||||||
'generic_relations_regress': [
|
"generic_relations_regress": [
|
||||||
'"Link" has no attribute',
|
'"Link" has no attribute',
|
||||||
],
|
],
|
||||||
'httpwrappers': [
|
"httpwrappers": [
|
||||||
'Argument 2 to "appendlist" of "QueryDict"',
|
'Argument 2 to "appendlist" of "QueryDict"',
|
||||||
'Incompatible types in assignment (expression has type "int", target has type "Union[str, List[str]]")',
|
'Incompatible types in assignment (expression has type "int", target has type "Union[str, List[str]]")',
|
||||||
'Argument 1 to "fromkeys" of "QueryDict" has incompatible type "int"',
|
'Argument 1 to "fromkeys" of "QueryDict" has incompatible type "int"',
|
||||||
],
|
],
|
||||||
'humanize_tests': [
|
"humanize_tests": [
|
||||||
'Argument 1 to "append" of "list" has incompatible type "None"; expected "str"',
|
'Argument 1 to "append" of "list" has incompatible type "None"; expected "str"',
|
||||||
],
|
],
|
||||||
'lookup': [
|
"lookup": [
|
||||||
'Unexpected keyword argument "headline__startswith" for "in_bulk" of',
|
'Unexpected keyword argument "headline__startswith" for "in_bulk" of',
|
||||||
'is called with more than one field',
|
"is called with more than one field",
|
||||||
"Cannot resolve keyword 'pub_date_year' into field",
|
"Cannot resolve keyword 'pub_date_year' into field",
|
||||||
"Cannot resolve keyword 'blahblah' into field",
|
"Cannot resolve keyword 'blahblah' into field",
|
||||||
],
|
],
|
||||||
'logging_tests': [
|
"logging_tests": [
|
||||||
re.compile(r'Argument [0-9] to "makeRecord" of "Logger"'),
|
re.compile(r'Argument [0-9] to "makeRecord" of "Logger"'),
|
||||||
'"LogRecord" has no attribute "request"',
|
'"LogRecord" has no attribute "request"',
|
||||||
],
|
],
|
||||||
'm2m_regress': [
|
"m2m_regress": [
|
||||||
"Cannot resolve keyword 'porcupine' into field",
|
"Cannot resolve keyword 'porcupine' into field",
|
||||||
'Argument 1 to "set" of "RelatedManager" has incompatible type "int"',
|
'Argument 1 to "set" of "RelatedManager" has incompatible type "int"',
|
||||||
],
|
],
|
||||||
'mail': [
|
"mail": [
|
||||||
'List item 1 has incompatible type "None"; expected "str"',
|
'List item 1 has incompatible type "None"; expected "str"',
|
||||||
'Incompatible types in assignment '
|
"Incompatible types in assignment "
|
||||||
+ '(expression has type "bool", variable has type "Union[SMTP_SSL, SMTP, None]")',
|
+ '(expression has type "bool", variable has type "Union[SMTP_SSL, SMTP, None]")',
|
||||||
],
|
],
|
||||||
'messages_tests': [
|
"messages_tests": [
|
||||||
'List item 0 has incompatible type "Dict[str, Message]"; expected "Message"',
|
'List item 0 has incompatible type "Dict[str, Message]"; expected "Message"',
|
||||||
'Too many arguments',
|
"Too many arguments",
|
||||||
'CustomRequest',
|
"CustomRequest",
|
||||||
],
|
],
|
||||||
'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")',
|
'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',
|
||||||
'"add" of "RelatedManager" has incompatible type "Article"; expected "Union[Publication, int]"',
|
'"add" of "RelatedManager" has incompatible type "Article"; expected "Union[Publication, int]"',
|
||||||
],
|
],
|
||||||
'many_to_one': [
|
"many_to_one": [
|
||||||
'Incompatible type for "parent" of "Child" (got "None", expected "Union[Parent, Combinable]")',
|
'Incompatible type for "parent" of "Child" (got "None", expected "Union[Parent, Combinable]")',
|
||||||
'Incompatible type for "parent" of "Child" (got "Child", expected "Union[Parent, Combinable]")',
|
'Incompatible type for "parent" of "Child" (got "Child", expected "Union[Parent, Combinable]")',
|
||||||
'expression has type "List[<nothing>]", variable has type "RelatedManager[Article]"',
|
'expression has type "List[<nothing>]", variable has type "RelatedManager[Article]"',
|
||||||
'"Reporter" has no attribute "cached_query"',
|
'"Reporter" has no attribute "cached_query"',
|
||||||
'to "add" of "RelatedManager" has incompatible type "Reporter"; expected "Union[Article, int]"',
|
'to "add" of "RelatedManager" has incompatible type "Reporter"; expected "Union[Article, int]"',
|
||||||
],
|
],
|
||||||
'middleware_exceptions': [
|
"middleware_exceptions": [
|
||||||
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any]"; expected "str"'
|
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any]"; expected "str"'
|
||||||
],
|
],
|
||||||
'migrate_signals': [
|
"migrate_signals": [
|
||||||
'Value of type "Optional[Any]" is not indexable',
|
'Value of type "Optional[Any]" is not indexable',
|
||||||
'Argument 1 to "set" has incompatible type "Optional[Any]"; expected "Iterable[Any]"',
|
'Argument 1 to "set" has incompatible type "Optional[Any]"; expected "Iterable[Any]"',
|
||||||
],
|
],
|
||||||
'migrations': [
|
"migrations": [
|
||||||
'FakeMigration',
|
"FakeMigration",
|
||||||
'FakeLoader',
|
"FakeLoader",
|
||||||
'"Manager[Any]" has no attribute "args"',
|
'"Manager[Any]" has no attribute "args"',
|
||||||
'Dict entry 0 has incompatible type "Any"',
|
'Dict entry 0 has incompatible type "Any"',
|
||||||
'Argument 1 to "append" of "list" has incompatible type',
|
'Argument 1 to "append" of "list" has incompatible type',
|
||||||
'base class "Model" defined the type as "BaseManager[Any]"',
|
'base class "Model" defined the type as "BaseManager[Any]"',
|
||||||
'Argument 1 to "RunPython" has incompatible type "str"',
|
'Argument 1 to "RunPython" has incompatible type "str"',
|
||||||
|
|
||||||
],
|
],
|
||||||
'model_fields': [
|
"model_fields": [
|
||||||
'Item "Field[Any, Any]" of "Union[Field[Any, Any], ForeignObjectRel]" has no attribute',
|
'Item "Field[Any, Any]" of "Union[Field[Any, Any], ForeignObjectRel]" has no attribute',
|
||||||
'Incompatible types in assignment (expression has type "Type[Person',
|
'Incompatible types in assignment (expression has type "Type[Person',
|
||||||
'Incompatible types in assignment (expression has type "FloatModel", variable has type',
|
'Incompatible types in assignment (expression has type "FloatModel", variable has type',
|
||||||
@@ -291,186 +319,168 @@ IGNORED_ERRORS = {
|
|||||||
'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"',
|
'"Child" has no attribute "get_foo_display"',
|
||||||
],
|
],
|
||||||
'model_forms': [
|
"model_forms": [
|
||||||
'"render" of "Widget"',
|
'"render" of "Widget"',
|
||||||
"Module 'django.core.validators' has no attribute 'ValidationError'",
|
"Module 'django.core.validators' has no attribute 'ValidationError'",
|
||||||
'Incompatible types in assignment',
|
"Incompatible types in assignment",
|
||||||
'NewForm',
|
"NewForm",
|
||||||
'"type" has no attribute "base_fields"',
|
'"type" has no attribute "base_fields"',
|
||||||
'Argument "instance" to "InvalidModelForm" has incompatible type "Type[Category]"',
|
'Argument "instance" to "InvalidModelForm" has incompatible type "Type[Category]"',
|
||||||
],
|
],
|
||||||
'model_indexes': [
|
"model_indexes": ['Argument "condition" to "Index" has incompatible type "str"; expected "Optional[Q]"'],
|
||||||
'Argument "condition" to "Index" has incompatible type "str"; expected "Optional[Q]"'
|
"model_inheritance": [
|
||||||
],
|
|
||||||
'model_inheritance': [
|
|
||||||
'base class "AbstractBase" defined',
|
'base class "AbstractBase" defined',
|
||||||
'base class "AbstractModel" defined',
|
'base class "AbstractModel" defined',
|
||||||
'Definition of "name" in base class "ConcreteParent"',
|
'Definition of "name" in base class "ConcreteParent"',
|
||||||
' Definition of "name" in base class "AbstractParent"',
|
' Definition of "name" in base class "AbstractParent"',
|
||||||
'referent_references',
|
"referent_references",
|
||||||
"Cannot resolve keyword 'attached_comment_set' into field"
|
"Cannot resolve keyword 'attached_comment_set' into field",
|
||||||
],
|
],
|
||||||
'model_meta': [
|
"model_meta": ['List item 0 has incompatible type "str"; expected "Union[Field[Any, Any], ForeignObjectRel]"'],
|
||||||
'List item 0 has incompatible type "str"; expected "Union[Field[Any, Any], ForeignObjectRel]"'
|
"model_regress": [
|
||||||
],
|
|
||||||
'model_regress': [
|
|
||||||
'Incompatible type for "department" of "Worker"',
|
'Incompatible type for "department" of "Worker"',
|
||||||
'"PickledModel" has no attribute',
|
'"PickledModel" has no attribute',
|
||||||
'"Department" has no attribute "evaluate"',
|
'"Department" has no attribute "evaluate"',
|
||||||
'Unsupported target for indexed assignment',
|
"Unsupported target for indexed assignment",
|
||||||
],
|
],
|
||||||
'model_formsets_regress': [
|
"model_formsets_regress": [
|
||||||
'Incompatible types in assignment (expression has type "int", target has type "str")',
|
'Incompatible types in assignment (expression has type "int", target has type "str")',
|
||||||
],
|
],
|
||||||
'model_options': [
|
"model_options": [
|
||||||
'expression has type "Dict[str, Type[Model]]", target has type "OrderedDict',
|
'expression has type "Dict[str, Type[Model]]", target has type "OrderedDict',
|
||||||
],
|
],
|
||||||
'model_enums': [
|
"model_enums": [
|
||||||
"'bool' is not a valid base class",
|
"'bool' is not a valid base class",
|
||||||
],
|
],
|
||||||
'null_queries': [
|
"null_queries": ["Cannot resolve keyword 'foo' into field"],
|
||||||
"Cannot resolve keyword 'foo' into field"
|
"order_with_respect_to": [
|
||||||
],
|
|
||||||
'order_with_respect_to': [
|
|
||||||
'"Dimension" has no attribute "set_component_order"',
|
'"Dimension" has no attribute "set_component_order"',
|
||||||
],
|
],
|
||||||
'one_to_one': [
|
"one_to_one": [
|
||||||
'expression has type "None", variable has type "UndergroundBar"',
|
'expression has type "None", variable has type "UndergroundBar"',
|
||||||
'Item "OneToOneField[Union[Place, Combinable], Place]" '
|
'Item "OneToOneField[Union[Place, Combinable], Place]" '
|
||||||
+ 'of "Union[OneToOneField[Union[Place, Combinable], Place], Any]"',
|
+ 'of "Union[OneToOneField[Union[Place, Combinable], Place], Any]"',
|
||||||
],
|
],
|
||||||
'pagination': [
|
"pagination": [
|
||||||
'"int" not callable',
|
'"int" not callable',
|
||||||
],
|
],
|
||||||
'postgres_tests': [
|
"postgres_tests": [
|
||||||
'DummyArrayField',
|
"DummyArrayField",
|
||||||
'DummyJSONField',
|
"DummyJSONField",
|
||||||
'Incompatible types in assignment (expression has type "Type[Field[Any, Any]]',
|
'Incompatible types in assignment (expression has type "Type[Field[Any, Any]]',
|
||||||
'Argument "encoder" to "JSONField" has incompatible type "DjangoJSONEncoder";',
|
'Argument "encoder" to "JSONField" has incompatible type "DjangoJSONEncoder";',
|
||||||
'("None" and "SearchQuery")',
|
'("None" and "SearchQuery")',
|
||||||
],
|
],
|
||||||
'properties': [
|
"properties": [re.compile('Unexpected attribute "(full_name|full_name_2)" for model "Person"')],
|
||||||
re.compile('Unexpected attribute "(full_name|full_name_2)" for model "Person"')
|
"prefetch_related": [
|
||||||
],
|
|
||||||
'prefetch_related': [
|
|
||||||
'"Person" has no attribute "houses_lst"',
|
'"Person" has no attribute "houses_lst"',
|
||||||
'"Book" has no attribute "first_authors"',
|
'"Book" has no attribute "first_authors"',
|
||||||
'"Book" has no attribute "the_authors"',
|
'"Book" has no attribute "the_authors"',
|
||||||
'Incompatible types in assignment (expression has type "List[Room]", variable has type "Manager[Room]")',
|
'Incompatible types in assignment (expression has type "List[Room]", variable has type "Manager[Room]")',
|
||||||
'Item "Room" of "Optional[Room]" has no attribute "house_attr"',
|
'Item "Room" of "Optional[Room]" has no attribute "house_attr"',
|
||||||
'Item "Room" of "Optional[Room]" has no attribute "main_room_of_attr"',
|
'Item "Room" of "Optional[Room]" has no attribute "main_room_of_attr"',
|
||||||
'Argument 2 to "Prefetch" has incompatible type "ValuesQuerySet'
|
'Argument 2 to "Prefetch" has incompatible type "ValuesQuerySet',
|
||||||
],
|
],
|
||||||
'proxy_models': [
|
"proxy_models": ["Incompatible types in assignment", 'in base class "User"'],
|
||||||
'Incompatible types in assignment',
|
"queries": [
|
||||||
'in base class "User"'
|
|
||||||
],
|
|
||||||
'queries': [
|
|
||||||
'Incompatible types in assignment (expression has type "None", variable has type "str")',
|
'Incompatible types in assignment (expression has type "None", variable has type "str")',
|
||||||
'Invalid index type "Optional[str]" for "Dict[str, int]"; expected type "str"',
|
'Invalid index type "Optional[str]" for "Dict[str, int]"; expected type "str"',
|
||||||
'Unsupported operand types for & ("Manager[Author]" and "Manager[Tag]")',
|
'Unsupported operand types for & ("Manager[Author]" and "Manager[Tag]")',
|
||||||
'Unsupported operand types for | ("Manager[Author]" and "Manager[Tag]")',
|
'Unsupported operand types for | ("Manager[Author]" and "Manager[Tag]")',
|
||||||
'ObjectA',
|
"ObjectA",
|
||||||
"'flat' and 'named' can't be used together",
|
"'flat' and 'named' can't be used together",
|
||||||
'"Collection[Any]" has no attribute "explain"',
|
'"Collection[Any]" has no attribute "explain"',
|
||||||
"Cannot resolve keyword 'unknown_field' into field",
|
"Cannot resolve keyword 'unknown_field' into field",
|
||||||
'Incompatible type for lookup \'tag\': (got "str", expected "Union[Tag, int, None]")',
|
'Incompatible type for lookup \'tag\': (got "str", expected "Union[Tag, int, None]")',
|
||||||
'No overload variant of "__getitem__" of "QuerySet" matches argument type "str"',
|
'No overload variant of "__getitem__" of "QuerySet" matches argument type "str"',
|
||||||
],
|
],
|
||||||
'requests': [
|
"requests": [
|
||||||
'Incompatible types in assignment (expression has type "Dict[str, str]", variable has type "QueryDict")'
|
'Incompatible types in assignment (expression has type "Dict[str, str]", variable has type "QueryDict")'
|
||||||
],
|
],
|
||||||
'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"',
|
'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"
|
"settings_tests": ['Argument 1 to "Settings" has incompatible type "Optional[str]"; expected "str"'],
|
||||||
],
|
"shortcuts": [
|
||||||
'settings_tests': [
|
|
||||||
'Argument 1 to "Settings" has incompatible type "Optional[str]"; expected "str"'
|
|
||||||
],
|
|
||||||
'shortcuts': [
|
|
||||||
'error: "Context" has no attribute "request"',
|
'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]";'
|
"sites_framework": [
|
||||||
],
|
|
||||||
'sites_framework': [
|
|
||||||
'expression has type "CurrentSiteManager[CustomArticle]", base class "AbstractArticle"',
|
'expression has type "CurrentSiteManager[CustomArticle]", base class "AbstractArticle"',
|
||||||
"Name 'Optional' is not defined",
|
"Name 'Optional' is not defined",
|
||||||
],
|
],
|
||||||
'sites_tests': [
|
"sites_tests": [
|
||||||
'"RequestSite" of "Union[Site, RequestSite]" has no attribute "id"',
|
'"RequestSite" of "Union[Site, RequestSite]" has no attribute "id"',
|
||||||
],
|
],
|
||||||
'syndication_tests': [
|
"syndication_tests": [
|
||||||
'Argument 1 to "add_domain" has incompatible type "*Tuple[object, ...]"',
|
'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")',
|
||||||
'"AbstractBaseSession" has no attribute',
|
'"AbstractBaseSession" has no attribute',
|
||||||
'"None" not callable',
|
'"None" not callable',
|
||||||
],
|
],
|
||||||
'select_related': [
|
"select_for_update": ['"Thread" has no attribute "isAlive"'],
|
||||||
|
"select_related": [
|
||||||
'Item "ForeignKey[Union[Genus, Combinable], Genus]" '
|
'Item "ForeignKey[Union[Genus, Combinable], Genus]" '
|
||||||
+ 'of "Union[ForeignKey[Union[Genus, Combinable], Genus], Any]"'
|
+ 'of "Union[ForeignKey[Union[Genus, Combinable], Genus], Any]"'
|
||||||
],
|
],
|
||||||
'select_related_onetoone': [
|
"select_related_onetoone": [
|
||||||
'Incompatible types in assignment (expression has type "Parent2", variable has type "Parent1")',
|
'Incompatible types in assignment (expression has type "Parent2", variable has type "Parent1")',
|
||||||
'"Parent1" has no attribute'
|
'"Parent1" has no attribute',
|
||||||
],
|
],
|
||||||
'servers': [
|
"servers": [
|
||||||
re.compile('Argument [0-9] to "WSGIRequestHandler"'),
|
re.compile('Argument [0-9] to "WSGIRequestHandler"'),
|
||||||
'"HTTPResponse" has no attribute',
|
'"HTTPResponse" has no attribute',
|
||||||
'"type" has no attribute',
|
'"type" has no attribute',
|
||||||
'"WSGIRequest" has no attribute "makefile"',
|
'"WSGIRequest" has no attribute "makefile"',
|
||||||
'LiveServerAddress',
|
"LiveServerAddress",
|
||||||
'"Stub" has no attribute "makefile"',
|
'"Stub" has no attribute "makefile"',
|
||||||
],
|
],
|
||||||
'serializers': [
|
"serializers": [
|
||||||
'"Model" has no attribute "data"',
|
'"Model" has no attribute "data"',
|
||||||
'"Iterable[Any]" has no attribute "content"',
|
'"Iterable[Any]" has no attribute "content"',
|
||||||
re.compile(r'Argument 1 to "(serialize|deserialize)" has incompatible type "None"; expected "str"')
|
re.compile(r'Argument 1 to "(serialize|deserialize)" has incompatible type "None"; expected "str"'),
|
||||||
],
|
],
|
||||||
'string_lookup': [
|
"string_lookup": [
|
||||||
'"Bar" has no attribute "place"',
|
'"Bar" has no attribute "place"',
|
||||||
],
|
],
|
||||||
'test_utils': [
|
"test_utils": [
|
||||||
'"PossessedCar" has no attribute "color"',
|
'"PossessedCar" has no attribute "color"',
|
||||||
'expression has type "None", variable has type "List[str]"',
|
'expression has type "None", variable has type "List[str]"',
|
||||||
],
|
],
|
||||||
'test_client': [
|
"test_client": ['(expression has type "HttpResponse", variable has type "StreamingHttpResponse")'],
|
||||||
'(expression has type "HttpResponse", variable has type "StreamingHttpResponse")'
|
"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"',
|
'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]")'
|
||||||
],
|
],
|
||||||
'urlpatterns': [
|
"urlpatterns": [
|
||||||
'"object" not callable',
|
'"object" not callable',
|
||||||
'"None" not callable',
|
'"None" not callable',
|
||||||
'Argument 2 to "path" has incompatible type "Callable[[Any], None]"',
|
'Argument 2 to "path" has incompatible type "Callable[[Any], None]"',
|
||||||
'Incompatible return value type (got "None", expected "HttpResponseBase")',
|
'Incompatible return value type (got "None", expected "HttpResponseBase")',
|
||||||
],
|
],
|
||||||
'urlpatterns_reverse': [
|
"urlpatterns_reverse": [
|
||||||
'No overload variant of "path" matches argument types "str", "None"',
|
'No overload variant of "path" matches argument types "str", "None"',
|
||||||
'No overload variant of "zip" matches argument types "Any", "object"',
|
'No overload variant of "zip" matches argument types "Any", "object"',
|
||||||
'Argument 1 to "get_callable" has incompatible type "int"'
|
'Argument 1 to "get_callable" has incompatible type "int"',
|
||||||
],
|
],
|
||||||
'utils_tests': [
|
"utils_tests": [
|
||||||
'Argument 1 to "activate" has incompatible type "None"; expected "Union[tzinfo, str]"',
|
'Argument 1 to "activate" has incompatible type "None"; expected "Union[tzinfo, str]"',
|
||||||
'Argument 1 to "activate" has incompatible type "Optional[str]"; expected "str"',
|
'Argument 1 to "activate" has incompatible type "Optional[str]"; expected "str"',
|
||||||
'Incompatible types in assignment (expression has type "None", base class "object" defined the type as',
|
'Incompatible types in assignment (expression has type "None", base class "object" defined the type as',
|
||||||
'Class',
|
"Class",
|
||||||
'has no attribute "cp"',
|
'has no attribute "cp"',
|
||||||
'Argument "name" to "cached_property" has incompatible type "int"; expected "Optional[str]"',
|
'Argument "name" to "cached_property" has incompatible type "int"; expected "Optional[str]"',
|
||||||
'has no attribute "sort"',
|
'has no attribute "sort"',
|
||||||
'Unsupported target for indexed assignment',
|
"Unsupported target for indexed assignment",
|
||||||
'defined the type as "None"',
|
'defined the type as "None"',
|
||||||
'Argument 1 to "Path" has incompatible type "Optional[str]"',
|
'Argument 1 to "Path" has incompatible type "Optional[str]"',
|
||||||
'"None" not callable',
|
'"None" not callable',
|
||||||
@@ -480,18 +490,18 @@ IGNORED_ERRORS = {
|
|||||||
'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"',
|
'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'",
|
||||||
'Value of type "Optional[List[str]]" is not indexable',
|
'Value of type "Optional[List[str]]" is not indexable',
|
||||||
'ExceptionUser',
|
"ExceptionUser",
|
||||||
'view_tests.tests.test_debug.User',
|
"view_tests.tests.test_debug.User",
|
||||||
'Exception must be derived from BaseException',
|
"Exception must be derived from BaseException",
|
||||||
"No binding for nonlocal 'tb_frames' found",
|
"No binding for nonlocal 'tb_frames' found",
|
||||||
],
|
],
|
||||||
'validation': [
|
"validation": [
|
||||||
'has no attribute "name"',
|
'has no attribute "name"',
|
||||||
],
|
],
|
||||||
'wsgi': [
|
"wsgi": [
|
||||||
'"HttpResponse" has no attribute "block_size"',
|
'"HttpResponse" has no attribute "block_size"',
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -500,9 +510,9 @@ IGNORED_ERRORS = {
|
|||||||
def check_if_custom_ignores_are_covered_by_common() -> None:
|
def check_if_custom_ignores_are_covered_by_common() -> None:
|
||||||
from scripts.typecheck_tests import is_pattern_fits
|
from scripts.typecheck_tests import is_pattern_fits
|
||||||
|
|
||||||
common_ignore_patterns = IGNORED_ERRORS['__common__']
|
common_ignore_patterns = IGNORED_ERRORS["__common__"]
|
||||||
for module_name, patterns in IGNORED_ERRORS.items():
|
for module_name, patterns in IGNORED_ERRORS.items():
|
||||||
if module_name == '__common__':
|
if module_name == "__common__":
|
||||||
continue
|
continue
|
||||||
for pattern in patterns:
|
for pattern in patterns:
|
||||||
for common_pattern in common_ignore_patterns:
|
for common_pattern in common_ignore_patterns:
|
||||||
|
|||||||
31
scripts/git_helpers.py
Normal file
31
scripts/git_helpers.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import shutil
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from git import RemoteProgress, Repo
|
||||||
|
|
||||||
|
from scripts.paths import DJANGO_SOURCE_DIRECTORY
|
||||||
|
|
||||||
|
|
||||||
|
class ProgressPrinter(RemoteProgress):
|
||||||
|
def line_dropped(self, line: str) -> None:
|
||||||
|
print(line)
|
||||||
|
|
||||||
|
def update(self, op_code, cur_count, max_count=None, message=""):
|
||||||
|
print(self._cur_line)
|
||||||
|
|
||||||
|
|
||||||
|
def checkout_django_branch(django_version: str, commit_sha: Optional[str]) -> Repo:
|
||||||
|
branch = f"stable/{django_version}.x"
|
||||||
|
if DJANGO_SOURCE_DIRECTORY.exists():
|
||||||
|
shutil.rmtree(DJANGO_SOURCE_DIRECTORY)
|
||||||
|
DJANGO_SOURCE_DIRECTORY.mkdir(exist_ok=True, parents=False)
|
||||||
|
repo = Repo.clone_from(
|
||||||
|
"https://github.com/django/django.git",
|
||||||
|
DJANGO_SOURCE_DIRECTORY,
|
||||||
|
progress=ProgressPrinter(),
|
||||||
|
branch=branch,
|
||||||
|
depth=100,
|
||||||
|
)
|
||||||
|
if commit_sha and repo.head.commit.hexsha != commit_sha:
|
||||||
|
repo.remote("origin").fetch(branch, progress=ProgressPrinter(), depth=100)
|
||||||
|
repo.git.checkout(commit_sha)
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
[mypy]
|
|
||||||
strict_optional = True
|
|
||||||
ignore_missing_imports = True
|
|
||||||
check_untyped_defs = True
|
|
||||||
warn_no_return = False
|
|
||||||
show_traceback = True
|
|
||||||
allow_redefinition = True
|
|
||||||
incremental = True
|
|
||||||
|
|
||||||
plugins =
|
|
||||||
mypy_django_plugin.main
|
|
||||||
|
|
||||||
[mypy.plugins.django-stubs]
|
|
||||||
django_settings_module = 'scripts.django_tests_settings'
|
|
||||||
4
scripts/paths.py
Normal file
4
scripts/paths.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
PROJECT_DIRECTORY = Path(__file__).parent.parent
|
||||||
|
DJANGO_SOURCE_DIRECTORY = PROJECT_DIRECTORY / "django-source" # type: Path
|
||||||
15
scripts/stubgen-django.py
Normal file
15
scripts/stubgen-django.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
|
from mypy.stubgen import generate_stubs, parse_options
|
||||||
|
|
||||||
|
from scripts.git_helpers import checkout_django_branch
|
||||||
|
from scripts.paths import DJANGO_SOURCE_DIRECTORY
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = ArgumentParser()
|
||||||
|
parser.add_argument("--django_version", required=True)
|
||||||
|
parser.add_argument("--commit_sha", required=False)
|
||||||
|
args = parser.parse_args()
|
||||||
|
checkout_django_branch(args.django_version, args.commit_sha)
|
||||||
|
stubgen_options = parse_options([f"{DJANGO_SOURCE_DIRECTORY}", "-o=stubgen"])
|
||||||
|
generate_stubs(stubgen_options)
|
||||||
@@ -3,28 +3,27 @@ from pytest_mypy_plugins.item import YamlTestItem
|
|||||||
|
|
||||||
|
|
||||||
def django_plugin_hook(test_item: YamlTestItem) -> None:
|
def django_plugin_hook(test_item: YamlTestItem) -> None:
|
||||||
custom_settings = test_item.parsed_test_data.get('custom_settings', '')
|
custom_settings = test_item.parsed_test_data.get("custom_settings", "")
|
||||||
installed_apps = test_item.parsed_test_data.get('installed_apps', None)
|
installed_apps = test_item.parsed_test_data.get("installed_apps", None)
|
||||||
|
|
||||||
if installed_apps and custom_settings:
|
if installed_apps and custom_settings:
|
||||||
raise ValueError('"installed_apps" and "custom_settings" are not compatible, please use one or the other')
|
raise ValueError('"installed_apps" and "custom_settings" are not compatible, please use one or the other')
|
||||||
|
|
||||||
if installed_apps is not None:
|
if installed_apps is not None:
|
||||||
# custom_settings is empty, add INSTALLED_APPS
|
# custom_settings is empty, add INSTALLED_APPS
|
||||||
installed_apps += ['django.contrib.contenttypes']
|
installed_apps += ["django.contrib.contenttypes"]
|
||||||
installed_apps_as_str = '(' + ','.join([repr(app) for app in installed_apps]) + ',)'
|
installed_apps_as_str = "(" + ",".join([repr(app) for app in installed_apps]) + ",)"
|
||||||
custom_settings += 'INSTALLED_APPS = ' + installed_apps_as_str
|
custom_settings += "INSTALLED_APPS = " + installed_apps_as_str
|
||||||
|
|
||||||
if 'SECRET_KEY' not in custom_settings:
|
if "SECRET_KEY" not in custom_settings:
|
||||||
custom_settings = 'SECRET_KEY = "1"\n' + custom_settings
|
custom_settings = 'SECRET_KEY = "1"\n' + custom_settings
|
||||||
|
|
||||||
django_settings_section = "\n[mypy.plugins.django-stubs]\n" \
|
django_settings_section = "\n[mypy.plugins.django-stubs]\n" "django_settings_module = mysettings"
|
||||||
"django_settings_module = mysettings"
|
|
||||||
if not test_item.additional_mypy_config:
|
if not test_item.additional_mypy_config:
|
||||||
test_item.additional_mypy_config = django_settings_section
|
test_item.additional_mypy_config = django_settings_section
|
||||||
else:
|
else:
|
||||||
if '[mypy.plugins.django-stubs]' not in test_item.additional_mypy_config:
|
if "[mypy.plugins.django-stubs]" not in test_item.additional_mypy_config:
|
||||||
test_item.additional_mypy_config += django_settings_section
|
test_item.additional_mypy_config += django_settings_section
|
||||||
|
|
||||||
mysettings_file = File(path='mysettings.py', content=custom_settings)
|
mysettings_file = File(path="mysettings.py", content=custom_settings)
|
||||||
test_item.files.append(mysettings_file)
|
test_item.files.append(mysettings_file)
|
||||||
|
|||||||
@@ -4,30 +4,27 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from pathlib import Path
|
from distutils import spawn
|
||||||
from typing import Dict, List, Pattern, Tuple, Union
|
from typing import Dict, List, Pattern, Union
|
||||||
|
|
||||||
from git import RemoteProgress, Repo
|
from scripts.enabled_test_modules import EXTERNAL_MODULES, IGNORED_ERRORS, IGNORED_MODULES, MOCK_OBJECTS
|
||||||
|
from scripts.git_helpers import checkout_django_branch
|
||||||
|
from scripts.paths import DJANGO_SOURCE_DIRECTORY, PROJECT_DIRECTORY
|
||||||
|
|
||||||
from scripts.enabled_test_modules import (
|
DJANGO_COMMIT_REFS: Dict[str, str] = {
|
||||||
EXTERNAL_MODULES, IGNORED_ERRORS, IGNORED_MODULES, MOCK_OBJECTS,
|
"2.2": "8093aaa8ff9dd7386a069c6eb49fcc1c5980c033",
|
||||||
)
|
"3.0": "44da7abda848f05caaed74f6a749038c87dedfda",
|
||||||
|
|
||||||
DJANGO_COMMIT_REFS: Dict[str, Tuple[str, str]] = {
|
|
||||||
'2.2': ('stable/2.2.x', '8093aaa8ff9dd7386a069c6eb49fcc1c5980c033'),
|
|
||||||
'3.0': ('stable/3.0.x', '44da7abda848f05caaed74f6a749038c87dedfda')
|
|
||||||
}
|
}
|
||||||
PROJECT_DIRECTORY = Path(__file__).parent.parent
|
|
||||||
DJANGO_SOURCE_DIRECTORY = PROJECT_DIRECTORY / 'django-sources' # type: Path
|
|
||||||
|
|
||||||
|
|
||||||
def get_unused_ignores(ignored_message_freq: Dict[str, Dict[Union[str, Pattern], int]]) -> List[str]:
|
def get_unused_ignores(ignored_message_freq: Dict[str, Dict[Union[str, Pattern], int]]) -> List[str]:
|
||||||
unused_ignores = []
|
unused_ignores = []
|
||||||
for root_key, patterns in IGNORED_ERRORS.items():
|
for root_key, patterns in IGNORED_ERRORS.items():
|
||||||
for pattern in patterns:
|
for pattern in patterns:
|
||||||
if (ignored_message_freq[root_key][pattern] == 0
|
if ignored_message_freq[root_key][pattern] == 0 and pattern not in itertools.chain(
|
||||||
and pattern not in itertools.chain(EXTERNAL_MODULES, MOCK_OBJECTS)):
|
EXTERNAL_MODULES, MOCK_OBJECTS
|
||||||
unused_ignores.append(f'{root_key}: {pattern}')
|
):
|
||||||
|
unused_ignores.append(f"{root_key}: {pattern}")
|
||||||
return unused_ignores
|
return unused_ignores
|
||||||
|
|
||||||
|
|
||||||
@@ -42,7 +39,7 @@ def is_pattern_fits(pattern: Union[Pattern, str], line: str):
|
|||||||
|
|
||||||
|
|
||||||
def is_ignored(line: str, test_folder_name: str, *, ignored_message_freqs: Dict[str, Dict[str, int]]) -> bool:
|
def is_ignored(line: str, test_folder_name: str, *, ignored_message_freqs: Dict[str, Dict[str, int]]) -> bool:
|
||||||
if 'runtests' in line:
|
if "runtests" in line:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if test_folder_name in IGNORED_MODULES:
|
if test_folder_name in IGNORED_MODULES:
|
||||||
@@ -53,83 +50,42 @@ def is_ignored(line: str, test_folder_name: str, *, ignored_message_freqs: Dict[
|
|||||||
ignored_message_freqs[test_folder_name][pattern] += 1
|
ignored_message_freqs[test_folder_name][pattern] += 1
|
||||||
return True
|
return True
|
||||||
|
|
||||||
for pattern in IGNORED_ERRORS['__common__']:
|
for pattern in IGNORED_ERRORS["__common__"]:
|
||||||
if is_pattern_fits(pattern, line):
|
if is_pattern_fits(pattern, line):
|
||||||
ignored_message_freqs['__common__'][pattern] += 1
|
ignored_message_freqs["__common__"][pattern] += 1
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def replace_with_clickable_location(error: str, abs_test_folder: Path) -> str:
|
if __name__ == "__main__":
|
||||||
raw_path, _, error_line = error.partition(': ')
|
|
||||||
fname, _, line_number = raw_path.partition(':')
|
|
||||||
|
|
||||||
try:
|
|
||||||
path = abs_test_folder.joinpath(fname).relative_to(PROJECT_DIRECTORY)
|
|
||||||
except ValueError:
|
|
||||||
# fail on travis, just show an error
|
|
||||||
return error
|
|
||||||
|
|
||||||
clickable_location = f'./{path}:{line_number or 1}'
|
|
||||||
return error.replace(raw_path, clickable_location)
|
|
||||||
|
|
||||||
|
|
||||||
class ProgressPrinter(RemoteProgress):
|
|
||||||
def line_dropped(self, line: str) -> None:
|
|
||||||
print(line)
|
|
||||||
|
|
||||||
def update(self, op_code, cur_count, max_count=None, message=''):
|
|
||||||
print(self._cur_line)
|
|
||||||
|
|
||||||
|
|
||||||
def get_django_repo_object(branch: str) -> Repo:
|
|
||||||
if not DJANGO_SOURCE_DIRECTORY.exists():
|
|
||||||
DJANGO_SOURCE_DIRECTORY.mkdir(exist_ok=True, parents=False)
|
|
||||||
return Repo.clone_from('https://github.com/django/django.git', DJANGO_SOURCE_DIRECTORY,
|
|
||||||
progress=ProgressPrinter(),
|
|
||||||
branch=branch,
|
|
||||||
depth=100)
|
|
||||||
else:
|
|
||||||
repo = Repo(DJANGO_SOURCE_DIRECTORY)
|
|
||||||
return repo
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
parser = ArgumentParser()
|
parser = ArgumentParser()
|
||||||
parser.add_argument('--django_version', choices=['2.2', '3.0'], required=True)
|
parser.add_argument("--django_version", default="3.0")
|
||||||
args = parser.parse_args()
|
django_version = parser.parse_args().django_version
|
||||||
|
subprocess.check_call([sys.executable, "-m", "pip", "install", f"Django=={django_version}.*"])
|
||||||
# install proper Django version
|
commit_sha = DJANGO_COMMIT_REFS[django_version]
|
||||||
subprocess.check_call([sys.executable, '-m', 'pip', 'install', f'Django=={args.django_version}.*'])
|
repo = checkout_django_branch(django_version, commit_sha)
|
||||||
|
mypy_config_file = (PROJECT_DIRECTORY / "mypy.ini").absolute()
|
||||||
branch, commit_sha = DJANGO_COMMIT_REFS[args.django_version]
|
mypy_cache_dir = PROJECT_DIRECTORY / ".mypy_cache"
|
||||||
repo = get_django_repo_object(branch)
|
tests_root = DJANGO_SOURCE_DIRECTORY / "tests"
|
||||||
if repo.head.commit.hexsha != commit_sha:
|
|
||||||
repo.remote('origin').fetch(branch, progress=ProgressPrinter(), depth=100)
|
|
||||||
repo.git.checkout(commit_sha)
|
|
||||||
|
|
||||||
mypy_config_file = (PROJECT_DIRECTORY / 'scripts' / 'mypy.ini').absolute()
|
|
||||||
mypy_cache_dir = Path(__file__).parent / '.mypy_cache'
|
|
||||||
tests_root = DJANGO_SOURCE_DIRECTORY / 'tests'
|
|
||||||
global_rc = 0
|
global_rc = 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mypy_options = ['--cache-dir', str(mypy_config_file.parent / '.mypy_cache'),
|
mypy_options = [
|
||||||
'--config-file', str(mypy_config_file),
|
"--cache-dir",
|
||||||
'--show-traceback',
|
str(mypy_cache_dir),
|
||||||
'--no-error-summary',
|
"--config-file",
|
||||||
'--hide-error-context'
|
str(mypy_config_file),
|
||||||
]
|
"--show-traceback",
|
||||||
|
"--no-error-summary",
|
||||||
|
"--hide-error-context",
|
||||||
|
]
|
||||||
mypy_options += [str(tests_root)]
|
mypy_options += [str(tests_root)]
|
||||||
|
mypy_executable = spawn.find_executable("mypy")
|
||||||
import distutils.spawn
|
|
||||||
|
|
||||||
mypy_executable = distutils.spawn.find_executable('mypy')
|
|
||||||
mypy_argv = [mypy_executable, *mypy_options]
|
mypy_argv = [mypy_executable, *mypy_options]
|
||||||
completed = subprocess.run(
|
completed = subprocess.run(
|
||||||
mypy_argv,
|
mypy_argv,
|
||||||
env={'PYTHONPATH': str(tests_root)},
|
env={"PYTHONPATH": str(tests_root), "TYPECHECK_TESTS": "1"},
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
)
|
)
|
||||||
@@ -140,20 +96,19 @@ if __name__ == '__main__':
|
|||||||
sorted_lines = sorted(output.splitlines())
|
sorted_lines = sorted(output.splitlines())
|
||||||
for line in sorted_lines:
|
for line in sorted_lines:
|
||||||
try:
|
try:
|
||||||
path_to_error = line.split(':')[0]
|
path_to_error = line.split(":")[0]
|
||||||
test_folder_name = path_to_error.split('/')[2]
|
test_folder_name = path_to_error.split("/")[2]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
test_folder_name = 'unknown'
|
test_folder_name = "unknown"
|
||||||
|
|
||||||
if not is_ignored(line, test_folder_name,
|
if not is_ignored(line, test_folder_name, ignored_message_freqs=ignored_message_freqs):
|
||||||
ignored_message_freqs=ignored_message_freqs):
|
|
||||||
global_rc = 1
|
global_rc = 1
|
||||||
print(line)
|
print(line)
|
||||||
|
|
||||||
unused_ignores = get_unused_ignores(ignored_message_freqs)
|
unused_ignores = get_unused_ignores(ignored_message_freqs)
|
||||||
if unused_ignores:
|
if unused_ignores:
|
||||||
print('UNUSED IGNORES ------------------------------------------------')
|
print("UNUSED IGNORES ------------------------------------------------")
|
||||||
print('\n'.join(unused_ignores))
|
print("\n".join(unused_ignores))
|
||||||
|
|
||||||
sys.exit(global_rc)
|
sys.exit(global_rc)
|
||||||
|
|
||||||
|
|||||||
20
setup.cfg
20
setup.cfg
@@ -1,19 +1,11 @@
|
|||||||
[isort]
|
|
||||||
skip =
|
|
||||||
django-sources
|
|
||||||
django-stubs
|
|
||||||
test-data
|
|
||||||
include_trailing_comma = true
|
|
||||||
multi_line_output = 5
|
|
||||||
wrap_length = 120
|
|
||||||
known_first_party = mypy_django_plugin
|
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
exclude =
|
exclude = .*/
|
||||||
django-sources
|
select = F401, Y
|
||||||
django-stubs
|
|
||||||
test-data
|
|
||||||
max_line_length = 120
|
max_line_length = 120
|
||||||
|
per-file-ignores =
|
||||||
|
*__init__.pyi: F401
|
||||||
|
base_user.pyi: Y003
|
||||||
|
models.pyi: Y003
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
license_file = LICENSE.txt
|
license_file = LICENSE.txt
|
||||||
|
|||||||
8
setup.py
8
setup.py
@@ -17,7 +17,7 @@ def find_stub_files(name: str) -> List[str]:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
with open("README.md", "r") as f:
|
with open("README.md") as f:
|
||||||
readme = f.read()
|
readme = f.read()
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
@@ -42,15 +42,17 @@ setup(
|
|||||||
packages=["django-stubs", *find_packages(exclude=["scripts"])],
|
packages=["django-stubs", *find_packages(exclude=["scripts"])],
|
||||||
package_data={"django-stubs": find_stub_files("django-stubs")},
|
package_data={"django-stubs": find_stub_files("django-stubs")},
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Development Status :: 3 - Alpha",
|
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
"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",
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Typing :: Typed",
|
||||||
"Framework :: Django",
|
"Framework :: Django",
|
||||||
"Framework :: Django :: 2.2",
|
"Framework :: Django :: 2.2",
|
||||||
"Framework :: Django :: 3.0",
|
"Framework :: Django :: 3.0",
|
||||||
"Typing :: Typed",
|
"Framework :: Django :: 3.1",
|
||||||
],
|
],
|
||||||
project_urls={
|
project_urls={
|
||||||
"Release notes": "https://github.com/typeddjango/django-stubs/releases",
|
"Release notes": "https://github.com/typeddjango/django-stubs/releases",
|
||||||
|
|||||||
@@ -15,42 +15,41 @@ TEMPLATE = """usage: (config)
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'config_file_contents,message_part',
|
"config_file_contents,message_part",
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
None,
|
None,
|
||||||
'mypy config file is not specified or found',
|
"mypy config file is not specified or found",
|
||||||
id='missing-file',
|
id="missing-file",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
['[not-really-django-stubs]'],
|
["[not-really-django-stubs]"],
|
||||||
'no section [mypy.plugins.django-stubs]',
|
"no section [mypy.plugins.django-stubs]",
|
||||||
id='missing-section',
|
id="missing-section",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
['[mypy.plugins.django-stubs]',
|
["[mypy.plugins.django-stubs]", "\tnot_django_not_settings_module = badbadmodule"],
|
||||||
'\tnot_django_not_settings_module = badbadmodule'],
|
"the setting is not provided",
|
||||||
'the setting is not provided',
|
id="missing-settings-module",
|
||||||
id='missing-settings-module',
|
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
['[mypy.plugins.django-stubs]'],
|
["[mypy.plugins.django-stubs]"],
|
||||||
'the setting is not provided',
|
"the setting is not provided",
|
||||||
id='no-settings-given',
|
id="no-settings-given",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_misconfiguration_handling(capsys, config_file_contents, message_part):
|
def test_misconfiguration_handling(capsys, config_file_contents, message_part):
|
||||||
# type: (typing.Any, typing.List[str], str) -> None
|
# type: (typing.Any, typing.List[str], str) -> None
|
||||||
"""Invalid configuration raises `SystemExit` with a precise error message."""
|
"""Invalid configuration raises `SystemExit` with a precise error message."""
|
||||||
with tempfile.NamedTemporaryFile(mode='w+') as config_file:
|
with tempfile.NamedTemporaryFile(mode="w+") as config_file:
|
||||||
if not config_file_contents:
|
if not config_file_contents:
|
||||||
config_file.close()
|
config_file.close()
|
||||||
else:
|
else:
|
||||||
config_file.write('\n'.join(config_file_contents).expandtabs(4))
|
config_file.write("\n".join(config_file_contents).expandtabs(4))
|
||||||
config_file.seek(0)
|
config_file.seek(0)
|
||||||
|
|
||||||
with pytest.raises(SystemExit, match='2'):
|
with pytest.raises(SystemExit, match="2"):
|
||||||
extract_django_settings_module(config_file.name)
|
extract_django_settings_module(config_file.name)
|
||||||
|
|
||||||
error_message = TEMPLATE.format(message_part)
|
error_message = TEMPLATE.format(message_part)
|
||||||
@@ -60,14 +59,14 @@ def test_misconfiguration_handling(capsys, config_file_contents, message_part):
|
|||||||
def test_correct_configuration() -> None:
|
def test_correct_configuration() -> None:
|
||||||
"""Django settings module gets extracted given valid configuration."""
|
"""Django settings module gets extracted given valid configuration."""
|
||||||
config_file_contents = [
|
config_file_contents = [
|
||||||
'[mypy.plugins.django-stubs]',
|
"[mypy.plugins.django-stubs]",
|
||||||
'\tsome_other_setting = setting',
|
"\tsome_other_setting = setting",
|
||||||
'\tdjango_settings_module = my.module',
|
"\tdjango_settings_module = my.module",
|
||||||
]
|
]
|
||||||
with tempfile.NamedTemporaryFile(mode='w+') as config_file:
|
with tempfile.NamedTemporaryFile(mode="w+") as config_file:
|
||||||
config_file.write('\n'.join(config_file_contents).expandtabs(4))
|
config_file.write("\n".join(config_file_contents).expandtabs(4))
|
||||||
config_file.seek(0)
|
config_file.seek(0)
|
||||||
|
|
||||||
extracted = extract_django_settings_module(config_file.name)
|
extracted = extract_django_settings_module(config_file.name)
|
||||||
|
|
||||||
assert extracted == 'my.module'
|
assert extracted == "my.module"
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
class Blog(models.Model):
|
class Blog(models.Model):
|
||||||
num_posts = models.IntegerField()
|
num_posts = models.IntegerField()
|
||||||
text = models.CharField(max_length=100)
|
text = models.CharField(max_length=100)
|
||||||
|
|
||||||
- case: queryset_values_all_values
|
- case: queryset_values_all_values
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import Blog
|
from myapp.models import Blog
|
||||||
@@ -336,7 +336,7 @@
|
|||||||
class ChildUser(models.Model):
|
class ChildUser(models.Model):
|
||||||
objects = MyManager()
|
objects = MyManager()
|
||||||
|
|
||||||
- case: custom_manager_annotate_method_before_type_declaration
|
- case: custom_manager_annotate_method_before_type_declaration
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import ModelA, ModelB, ManagerA
|
from myapp.models import ModelA, ModelB, ManagerA
|
||||||
reveal_type(ModelA.objects) # N: Revealed type is 'myapp.models.ModelA_ManagerA1[myapp.models.ModelA]'
|
reveal_type(ModelA.objects) # N: Revealed type is 'myapp.models.ModelA_ManagerA1[myapp.models.ModelA]'
|
||||||
@@ -3,23 +3,18 @@
|
|||||||
import django.apps
|
import django.apps
|
||||||
import django.apps.config
|
import django.apps.config
|
||||||
import django.apps.registry
|
import django.apps.registry
|
||||||
import django.conf
|
|
||||||
import django.conf.global_settings
|
import django.conf.global_settings
|
||||||
import django.conf.locale
|
import django.conf.locale
|
||||||
import django.conf.urls
|
import django.conf.urls
|
||||||
import django.conf.urls.i18n
|
import django.conf.urls.i18n
|
||||||
import django.conf.urls.static
|
import django.conf.urls.static
|
||||||
import django.contrib
|
|
||||||
import django.contrib.admin
|
|
||||||
import django.contrib.admin.actions
|
import django.contrib.admin.actions
|
||||||
import django.contrib.admin.apps
|
import django.contrib.admin.apps
|
||||||
import django.contrib.admin.checks
|
import django.contrib.admin.checks
|
||||||
import django.contrib.admin.decorators
|
import django.contrib.admin.decorators
|
||||||
import django.contrib.admin.exceptions
|
|
||||||
import django.contrib.admin.filters
|
import django.contrib.admin.filters
|
||||||
import django.contrib.admin.forms
|
import django.contrib.admin.forms
|
||||||
import django.contrib.admin.helpers
|
import django.contrib.admin.helpers
|
||||||
import django.contrib.admin.migrations
|
|
||||||
import django.contrib.admin.models
|
import django.contrib.admin.models
|
||||||
import django.contrib.admin.options
|
import django.contrib.admin.options
|
||||||
import django.contrib.admin.sites
|
import django.contrib.admin.sites
|
||||||
@@ -38,12 +33,10 @@
|
|||||||
import django.contrib.admin.views.main
|
import django.contrib.admin.views.main
|
||||||
import django.contrib.admin.widgets
|
import django.contrib.admin.widgets
|
||||||
import django.contrib.admindocs
|
import django.contrib.admindocs
|
||||||
import django.contrib.admindocs.apps
|
|
||||||
import django.contrib.admindocs.middleware
|
import django.contrib.admindocs.middleware
|
||||||
import django.contrib.admindocs.urls
|
import django.contrib.admindocs.urls
|
||||||
import django.contrib.admindocs.utils
|
import django.contrib.admindocs.utils
|
||||||
import django.contrib.admindocs.views
|
import django.contrib.admindocs.views
|
||||||
import django.contrib.auth
|
|
||||||
import django.contrib.auth.admin
|
import django.contrib.auth.admin
|
||||||
import django.contrib.auth.apps
|
import django.contrib.auth.apps
|
||||||
import django.contrib.auth.backends
|
import django.contrib.auth.backends
|
||||||
@@ -55,12 +48,10 @@
|
|||||||
import django.contrib.auth.handlers
|
import django.contrib.auth.handlers
|
||||||
import django.contrib.auth.handlers.modwsgi
|
import django.contrib.auth.handlers.modwsgi
|
||||||
import django.contrib.auth.hashers
|
import django.contrib.auth.hashers
|
||||||
import django.contrib.auth.management
|
|
||||||
import django.contrib.auth.management.commands
|
import django.contrib.auth.management.commands
|
||||||
import django.contrib.auth.management.commands.changepassword
|
import django.contrib.auth.management.commands.changepassword
|
||||||
import django.contrib.auth.management.commands.createsuperuser
|
import django.contrib.auth.management.commands.createsuperuser
|
||||||
import django.contrib.auth.middleware
|
import django.contrib.auth.middleware
|
||||||
import django.contrib.auth.migrations
|
|
||||||
import django.contrib.auth.mixins
|
import django.contrib.auth.mixins
|
||||||
import django.contrib.auth.models
|
import django.contrib.auth.models
|
||||||
import django.contrib.auth.password_validation
|
import django.contrib.auth.password_validation
|
||||||
@@ -69,162 +60,28 @@
|
|||||||
import django.contrib.auth.urls
|
import django.contrib.auth.urls
|
||||||
import django.contrib.auth.validators
|
import django.contrib.auth.validators
|
||||||
import django.contrib.auth.views
|
import django.contrib.auth.views
|
||||||
import django.contrib.contenttypes
|
|
||||||
import django.contrib.contenttypes.admin
|
import django.contrib.contenttypes.admin
|
||||||
import django.contrib.contenttypes.apps
|
import django.contrib.contenttypes.apps
|
||||||
import django.contrib.contenttypes.checks
|
import django.contrib.contenttypes.checks
|
||||||
import django.contrib.contenttypes.fields
|
import django.contrib.contenttypes.fields
|
||||||
import django.contrib.contenttypes.forms
|
import django.contrib.contenttypes.forms
|
||||||
import django.contrib.contenttypes.management
|
|
||||||
import django.contrib.contenttypes.management.commands
|
import django.contrib.contenttypes.management.commands
|
||||||
import django.contrib.contenttypes.management.commands.remove_stale_contenttypes
|
import django.contrib.contenttypes.management.commands.remove_stale_contenttypes
|
||||||
import django.contrib.contenttypes.migrations
|
|
||||||
import django.contrib.contenttypes.models
|
import django.contrib.contenttypes.models
|
||||||
import django.contrib.contenttypes.views
|
import django.contrib.contenttypes.views
|
||||||
import django.contrib.flatpages
|
|
||||||
import django.contrib.flatpages.admin
|
|
||||||
import django.contrib.flatpages.apps
|
|
||||||
import django.contrib.flatpages.forms
|
import django.contrib.flatpages.forms
|
||||||
import django.contrib.flatpages.middleware
|
import django.contrib.flatpages.middleware
|
||||||
import django.contrib.flatpages.migrations
|
|
||||||
import django.contrib.flatpages.models
|
import django.contrib.flatpages.models
|
||||||
import django.contrib.flatpages.sitemaps
|
import django.contrib.flatpages.sitemaps
|
||||||
import django.contrib.flatpages.templatetags
|
import django.contrib.flatpages.templatetags
|
||||||
import django.contrib.flatpages.templatetags.flatpages
|
import django.contrib.flatpages.templatetags.flatpages
|
||||||
import django.contrib.flatpages.urls
|
import django.contrib.flatpages.urls
|
||||||
import django.contrib.flatpages.views
|
import django.contrib.flatpages.views
|
||||||
import django.contrib.gis
|
|
||||||
import django.contrib.gis.admin
|
|
||||||
import django.contrib.gis.admin.options
|
|
||||||
import django.contrib.gis.admin.widgets
|
|
||||||
import django.contrib.gis.apps
|
|
||||||
import django.contrib.gis.db
|
|
||||||
import django.contrib.gis.db.backends
|
|
||||||
import django.contrib.gis.db.backends.base
|
|
||||||
import django.contrib.gis.db.backends.base.adapter
|
|
||||||
import django.contrib.gis.db.backends.base.features
|
|
||||||
import django.contrib.gis.db.backends.base.models
|
|
||||||
import django.contrib.gis.db.backends.base.operations
|
|
||||||
import django.contrib.gis.db.backends.mysql
|
|
||||||
import django.contrib.gis.db.backends.mysql.base
|
|
||||||
import django.contrib.gis.db.backends.mysql.features
|
|
||||||
import django.contrib.gis.db.backends.mysql.introspection
|
|
||||||
import django.contrib.gis.db.backends.mysql.operations
|
|
||||||
import django.contrib.gis.db.backends.mysql.schema
|
|
||||||
import django.contrib.gis.db.backends.oracle
|
|
||||||
import django.contrib.gis.db.backends.oracle.adapter
|
|
||||||
import django.contrib.gis.db.backends.oracle.base
|
|
||||||
import django.contrib.gis.db.backends.oracle.features
|
|
||||||
import django.contrib.gis.db.backends.oracle.introspection
|
|
||||||
import django.contrib.gis.db.backends.oracle.models
|
|
||||||
import django.contrib.gis.db.backends.oracle.operations
|
|
||||||
import django.contrib.gis.db.backends.oracle.schema
|
|
||||||
import django.contrib.gis.db.backends.postgis
|
|
||||||
import django.contrib.gis.db.backends.postgis.adapter
|
|
||||||
import django.contrib.gis.db.backends.postgis.base
|
|
||||||
import django.contrib.gis.db.backends.postgis.const
|
|
||||||
import django.contrib.gis.db.backends.postgis.features
|
|
||||||
import django.contrib.gis.db.backends.postgis.introspection
|
|
||||||
import django.contrib.gis.db.backends.postgis.models
|
|
||||||
import django.contrib.gis.db.backends.postgis.operations
|
|
||||||
import django.contrib.gis.db.backends.postgis.pgraster
|
|
||||||
import django.contrib.gis.db.backends.postgis.schema
|
|
||||||
import django.contrib.gis.db.backends.spatialite
|
|
||||||
import django.contrib.gis.db.backends.spatialite.adapter
|
|
||||||
import django.contrib.gis.db.backends.spatialite.base
|
|
||||||
import django.contrib.gis.db.backends.spatialite.client
|
|
||||||
import django.contrib.gis.db.backends.spatialite.features
|
|
||||||
import django.contrib.gis.db.backends.spatialite.introspection
|
|
||||||
import django.contrib.gis.db.backends.spatialite.models
|
|
||||||
import django.contrib.gis.db.backends.spatialite.operations
|
|
||||||
import django.contrib.gis.db.backends.spatialite.schema
|
|
||||||
import django.contrib.gis.db.backends.utils
|
|
||||||
import django.contrib.gis.db.models
|
import django.contrib.gis.db.models
|
||||||
import django.contrib.gis.db.models.aggregates
|
|
||||||
import django.contrib.gis.db.models.fields
|
import django.contrib.gis.db.models.fields
|
||||||
import django.contrib.gis.db.models.functions
|
|
||||||
import django.contrib.gis.db.models.lookups
|
|
||||||
import django.contrib.gis.db.models.proxy
|
|
||||||
import django.contrib.gis.db.models.sql
|
|
||||||
import django.contrib.gis.db.models.sql.conversion
|
|
||||||
import django.contrib.gis.feeds
|
|
||||||
import django.contrib.gis.forms
|
|
||||||
import django.contrib.gis.forms.fields
|
|
||||||
import django.contrib.gis.forms.widgets
|
|
||||||
import django.contrib.gis.gdal
|
|
||||||
import django.contrib.gis.gdal.base
|
|
||||||
import django.contrib.gis.gdal.datasource
|
|
||||||
import django.contrib.gis.gdal.driver
|
|
||||||
import django.contrib.gis.gdal.envelope
|
|
||||||
import django.contrib.gis.gdal.error
|
|
||||||
import django.contrib.gis.gdal.feature
|
|
||||||
import django.contrib.gis.gdal.field
|
|
||||||
import django.contrib.gis.gdal.geometries
|
|
||||||
import django.contrib.gis.gdal.geomtype
|
|
||||||
import django.contrib.gis.gdal.layer
|
|
||||||
import django.contrib.gis.gdal.libgdal
|
|
||||||
import django.contrib.gis.gdal.prototypes
|
|
||||||
import django.contrib.gis.gdal.prototypes.ds
|
|
||||||
import django.contrib.gis.gdal.prototypes.errcheck
|
|
||||||
import django.contrib.gis.gdal.prototypes.generation
|
|
||||||
import django.contrib.gis.gdal.prototypes.geom
|
|
||||||
import django.contrib.gis.gdal.prototypes.raster
|
|
||||||
import django.contrib.gis.gdal.prototypes.srs
|
|
||||||
import django.contrib.gis.gdal.raster
|
|
||||||
import django.contrib.gis.gdal.raster.band
|
|
||||||
import django.contrib.gis.gdal.raster.base
|
|
||||||
import django.contrib.gis.gdal.raster.const
|
|
||||||
import django.contrib.gis.gdal.raster.source
|
|
||||||
import django.contrib.gis.gdal.srs
|
|
||||||
import django.contrib.gis.geoip2
|
|
||||||
import django.contrib.gis.geoip2.base
|
|
||||||
import django.contrib.gis.geoip2.resources
|
|
||||||
import django.contrib.gis.geometry
|
|
||||||
import django.contrib.gis.geos
|
|
||||||
import django.contrib.gis.geos.base
|
|
||||||
import django.contrib.gis.geos.collections
|
|
||||||
import django.contrib.gis.geos.coordseq
|
|
||||||
import django.contrib.gis.geos.error
|
|
||||||
import django.contrib.gis.geos.factory
|
|
||||||
import django.contrib.gis.geos.geometry
|
|
||||||
import django.contrib.gis.geos.io
|
|
||||||
import django.contrib.gis.geos.libgeos
|
|
||||||
import django.contrib.gis.geos.linestring
|
|
||||||
import django.contrib.gis.geos.mutable_list
|
|
||||||
import django.contrib.gis.geos.point
|
|
||||||
import django.contrib.gis.geos.polygon
|
|
||||||
import django.contrib.gis.geos.prepared
|
|
||||||
import django.contrib.gis.geos.prototypes
|
|
||||||
import django.contrib.gis.geos.prototypes.coordseq
|
|
||||||
import django.contrib.gis.geos.prototypes.errcheck
|
|
||||||
import django.contrib.gis.geos.prototypes.geom
|
|
||||||
import django.contrib.gis.geos.prototypes.io
|
|
||||||
import django.contrib.gis.geos.prototypes.misc
|
|
||||||
import django.contrib.gis.geos.prototypes.predicates
|
|
||||||
import django.contrib.gis.geos.prototypes.prepared
|
|
||||||
import django.contrib.gis.geos.prototypes.threadsafe
|
|
||||||
import django.contrib.gis.geos.prototypes.topology
|
|
||||||
import django.contrib.gis.measure
|
|
||||||
import django.contrib.gis.ptr
|
|
||||||
import django.contrib.gis.serializers
|
|
||||||
import django.contrib.gis.serializers.geojson
|
|
||||||
import django.contrib.gis.shortcuts
|
|
||||||
import django.contrib.gis.sitemaps
|
|
||||||
import django.contrib.gis.sitemaps.kml
|
|
||||||
import django.contrib.gis.sitemaps.views
|
|
||||||
import django.contrib.gis.utils
|
|
||||||
import django.contrib.gis.utils.layermapping
|
|
||||||
import django.contrib.gis.utils.ogrinfo
|
|
||||||
import django.contrib.gis.utils.ogrinspect
|
|
||||||
import django.contrib.gis.utils.srs
|
|
||||||
import django.contrib.gis.views
|
|
||||||
import django.contrib.humanize
|
|
||||||
import django.contrib.humanize.apps
|
|
||||||
import django.contrib.humanize.templatetags
|
import django.contrib.humanize.templatetags
|
||||||
import django.contrib.humanize.templatetags.humanize
|
import django.contrib.humanize.templatetags.humanize
|
||||||
import django.contrib.messages
|
|
||||||
import django.contrib.messages.api
|
import django.contrib.messages.api
|
||||||
import django.contrib.messages.apps
|
|
||||||
import django.contrib.messages.constants
|
import django.contrib.messages.constants
|
||||||
import django.contrib.messages.context_processors
|
import django.contrib.messages.context_processors
|
||||||
import django.contrib.messages.middleware
|
import django.contrib.messages.middleware
|
||||||
@@ -235,12 +92,10 @@
|
|||||||
import django.contrib.messages.storage.session
|
import django.contrib.messages.storage.session
|
||||||
import django.contrib.messages.utils
|
import django.contrib.messages.utils
|
||||||
import django.contrib.messages.views
|
import django.contrib.messages.views
|
||||||
import django.contrib.postgres
|
|
||||||
import django.contrib.postgres.aggregates
|
import django.contrib.postgres.aggregates
|
||||||
import django.contrib.postgres.aggregates.general
|
import django.contrib.postgres.aggregates.general
|
||||||
import django.contrib.postgres.aggregates.mixins
|
import django.contrib.postgres.aggregates.mixins
|
||||||
import django.contrib.postgres.aggregates.statistics
|
import django.contrib.postgres.aggregates.statistics
|
||||||
import django.contrib.postgres.apps
|
|
||||||
import django.contrib.postgres.constraints
|
import django.contrib.postgres.constraints
|
||||||
import django.contrib.postgres.fields
|
import django.contrib.postgres.fields
|
||||||
import django.contrib.postgres.fields.array
|
import django.contrib.postgres.fields.array
|
||||||
@@ -249,24 +104,16 @@
|
|||||||
import django.contrib.postgres.fields.jsonb
|
import django.contrib.postgres.fields.jsonb
|
||||||
import django.contrib.postgres.fields.mixins
|
import django.contrib.postgres.fields.mixins
|
||||||
import django.contrib.postgres.fields.ranges
|
import django.contrib.postgres.fields.ranges
|
||||||
import django.contrib.postgres.fields.utils
|
|
||||||
import django.contrib.postgres.functions
|
import django.contrib.postgres.functions
|
||||||
import django.contrib.postgres.indexes
|
import django.contrib.postgres.indexes
|
||||||
import django.contrib.postgres.lookups
|
import django.contrib.postgres.lookups
|
||||||
import django.contrib.postgres.operations
|
import django.contrib.postgres.operations
|
||||||
import django.contrib.postgres.search
|
import django.contrib.postgres.search
|
||||||
import django.contrib.postgres.serializers
|
|
||||||
import django.contrib.postgres.signals
|
import django.contrib.postgres.signals
|
||||||
import django.contrib.postgres.utils
|
|
||||||
import django.contrib.postgres.validators
|
import django.contrib.postgres.validators
|
||||||
import django.contrib.redirects
|
import django.contrib.redirects
|
||||||
import django.contrib.redirects.admin
|
|
||||||
import django.contrib.redirects.apps
|
|
||||||
import django.contrib.redirects.middleware
|
import django.contrib.redirects.middleware
|
||||||
import django.contrib.redirects.migrations
|
|
||||||
import django.contrib.redirects.models
|
import django.contrib.redirects.models
|
||||||
import django.contrib.sessions
|
|
||||||
import django.contrib.sessions.apps
|
|
||||||
import django.contrib.sessions.backends
|
import django.contrib.sessions.backends
|
||||||
import django.contrib.sessions.backends.base
|
import django.contrib.sessions.backends.base
|
||||||
import django.contrib.sessions.backends.cache
|
import django.contrib.sessions.backends.cache
|
||||||
@@ -276,35 +123,26 @@
|
|||||||
import django.contrib.sessions.backends.signed_cookies
|
import django.contrib.sessions.backends.signed_cookies
|
||||||
import django.contrib.sessions.base_session
|
import django.contrib.sessions.base_session
|
||||||
import django.contrib.sessions.exceptions
|
import django.contrib.sessions.exceptions
|
||||||
import django.contrib.sessions.management
|
|
||||||
import django.contrib.sessions.management.commands
|
import django.contrib.sessions.management.commands
|
||||||
import django.contrib.sessions.management.commands.clearsessions
|
import django.contrib.sessions.management.commands.clearsessions
|
||||||
import django.contrib.sessions.middleware
|
import django.contrib.sessions.middleware
|
||||||
import django.contrib.sessions.migrations
|
|
||||||
import django.contrib.sessions.models
|
import django.contrib.sessions.models
|
||||||
import django.contrib.sessions.serializers
|
import django.contrib.sessions.serializers
|
||||||
import django.contrib.sitemaps
|
|
||||||
import django.contrib.sitemaps.apps
|
|
||||||
import django.contrib.sitemaps.management
|
|
||||||
import django.contrib.sitemaps.management.commands
|
import django.contrib.sitemaps.management.commands
|
||||||
import django.contrib.sitemaps.management.commands.ping_google
|
import django.contrib.sitemaps.management.commands.ping_google
|
||||||
import django.contrib.sitemaps.views
|
import django.contrib.sitemaps.views
|
||||||
import django.contrib.sites
|
import django.contrib.sites
|
||||||
import django.contrib.sites.admin
|
|
||||||
import django.contrib.sites.apps
|
import django.contrib.sites.apps
|
||||||
import django.contrib.sites.management
|
import django.contrib.sites.management
|
||||||
import django.contrib.sites.managers
|
import django.contrib.sites.managers
|
||||||
import django.contrib.sites.middleware
|
import django.contrib.sites.middleware
|
||||||
import django.contrib.sites.migrations
|
|
||||||
import django.contrib.sites.models
|
import django.contrib.sites.models
|
||||||
import django.contrib.sites.requests
|
import django.contrib.sites.requests
|
||||||
import django.contrib.sites.shortcuts
|
import django.contrib.sites.shortcuts
|
||||||
import django.contrib.staticfiles
|
|
||||||
import django.contrib.staticfiles.apps
|
import django.contrib.staticfiles.apps
|
||||||
import django.contrib.staticfiles.checks
|
import django.contrib.staticfiles.checks
|
||||||
import django.contrib.staticfiles.finders
|
import django.contrib.staticfiles.finders
|
||||||
import django.contrib.staticfiles.handlers
|
import django.contrib.staticfiles.handlers
|
||||||
import django.contrib.staticfiles.management
|
|
||||||
import django.contrib.staticfiles.management.commands
|
import django.contrib.staticfiles.management.commands
|
||||||
import django.contrib.staticfiles.management.commands.collectstatic
|
import django.contrib.staticfiles.management.commands.collectstatic
|
||||||
import django.contrib.staticfiles.management.commands.findstatic
|
import django.contrib.staticfiles.management.commands.findstatic
|
||||||
@@ -317,11 +155,7 @@
|
|||||||
import django.contrib.staticfiles.utils
|
import django.contrib.staticfiles.utils
|
||||||
import django.contrib.staticfiles.views
|
import django.contrib.staticfiles.views
|
||||||
import django.contrib.syndication
|
import django.contrib.syndication
|
||||||
import django.contrib.syndication.apps
|
|
||||||
import django.contrib.syndication.views
|
import django.contrib.syndication.views
|
||||||
import django.core
|
|
||||||
import django.core.asgi
|
|
||||||
import django.core.cache
|
|
||||||
import django.core.cache.backends
|
import django.core.cache.backends
|
||||||
import django.core.cache.backends.base
|
import django.core.cache.backends.base
|
||||||
import django.core.cache.backends.db
|
import django.core.cache.backends.db
|
||||||
@@ -330,10 +164,7 @@
|
|||||||
import django.core.cache.backends.locmem
|
import django.core.cache.backends.locmem
|
||||||
import django.core.cache.backends.memcached
|
import django.core.cache.backends.memcached
|
||||||
import django.core.cache.utils
|
import django.core.cache.utils
|
||||||
import django.core.checks
|
|
||||||
import django.core.checks.async_checks
|
|
||||||
import django.core.checks.caches
|
import django.core.checks.caches
|
||||||
import django.core.checks.compatibility
|
|
||||||
import django.core.checks.database
|
import django.core.checks.database
|
||||||
import django.core.checks.messages
|
import django.core.checks.messages
|
||||||
import django.core.checks.model_checks
|
import django.core.checks.model_checks
|
||||||
@@ -357,21 +188,13 @@
|
|||||||
import django.core.files.uploadhandler
|
import django.core.files.uploadhandler
|
||||||
import django.core.files.utils
|
import django.core.files.utils
|
||||||
import django.core.handlers
|
import django.core.handlers
|
||||||
import django.core.handlers.asgi
|
|
||||||
import django.core.handlers.base
|
import django.core.handlers.base
|
||||||
import django.core.handlers.exception
|
import django.core.handlers.exception
|
||||||
import django.core.handlers.wsgi
|
import django.core.handlers.wsgi
|
||||||
import django.core.mail
|
|
||||||
import django.core.mail.backends
|
import django.core.mail.backends
|
||||||
import django.core.mail.backends.base
|
import django.core.mail.backends.base
|
||||||
import django.core.mail.backends.console
|
|
||||||
import django.core.mail.backends.dummy
|
|
||||||
import django.core.mail.backends.filebased
|
|
||||||
import django.core.mail.backends.locmem
|
|
||||||
import django.core.mail.backends.smtp
|
|
||||||
import django.core.mail.message
|
import django.core.mail.message
|
||||||
import django.core.mail.utils
|
import django.core.mail.utils
|
||||||
import django.core.management
|
|
||||||
import django.core.management.base
|
import django.core.management.base
|
||||||
import django.core.management.color
|
import django.core.management.color
|
||||||
import django.core.management.commands
|
import django.core.management.commands
|
||||||
@@ -388,16 +211,12 @@
|
|||||||
import django.core.serializers.base
|
import django.core.serializers.base
|
||||||
import django.core.serializers.json
|
import django.core.serializers.json
|
||||||
import django.core.serializers.python
|
import django.core.serializers.python
|
||||||
import django.core.serializers.pyyaml
|
|
||||||
import django.core.serializers.xml_serializer
|
|
||||||
import django.core.servers
|
import django.core.servers
|
||||||
import django.core.servers.basehttp
|
import django.core.servers.basehttp
|
||||||
import django.core.signals
|
import django.core.signals
|
||||||
import django.core.signing
|
import django.core.signing
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
import django.core.wsgi
|
import django.core.wsgi
|
||||||
import django.db
|
|
||||||
import django.db.backends
|
|
||||||
import django.db.backends.base
|
import django.db.backends.base
|
||||||
import django.db.backends.base.base
|
import django.db.backends.base.base
|
||||||
import django.db.backends.base.client
|
import django.db.backends.base.client
|
||||||
@@ -410,47 +229,22 @@
|
|||||||
import django.db.backends.ddl_references
|
import django.db.backends.ddl_references
|
||||||
import django.db.backends.dummy
|
import django.db.backends.dummy
|
||||||
import django.db.backends.dummy.base
|
import django.db.backends.dummy.base
|
||||||
import django.db.backends.dummy.features
|
|
||||||
import django.db.backends.mysql
|
import django.db.backends.mysql
|
||||||
import django.db.backends.mysql.base
|
|
||||||
import django.db.backends.mysql.client
|
import django.db.backends.mysql.client
|
||||||
import django.db.backends.mysql.compiler
|
|
||||||
import django.db.backends.mysql.creation
|
|
||||||
import django.db.backends.mysql.features
|
|
||||||
import django.db.backends.mysql.introspection
|
|
||||||
import django.db.backends.mysql.operations
|
|
||||||
import django.db.backends.mysql.schema
|
|
||||||
import django.db.backends.mysql.validation
|
|
||||||
import django.db.backends.oracle
|
|
||||||
import django.db.backends.oracle.base
|
|
||||||
import django.db.backends.oracle.client
|
|
||||||
import django.db.backends.oracle.creation
|
|
||||||
import django.db.backends.oracle.features
|
|
||||||
import django.db.backends.oracle.functions
|
|
||||||
import django.db.backends.oracle.introspection
|
|
||||||
import django.db.backends.oracle.operations
|
|
||||||
import django.db.backends.oracle.schema
|
|
||||||
import django.db.backends.oracle.utils
|
|
||||||
import django.db.backends.oracle.validation
|
|
||||||
import django.db.backends.postgresql
|
import django.db.backends.postgresql
|
||||||
import django.db.backends.postgresql.base
|
import django.db.backends.postgresql.base
|
||||||
import django.db.backends.postgresql.client
|
import django.db.backends.postgresql.client
|
||||||
import django.db.backends.postgresql.creation
|
import django.db.backends.postgresql.creation
|
||||||
import django.db.backends.postgresql.features
|
|
||||||
import django.db.backends.postgresql.introspection
|
|
||||||
import django.db.backends.postgresql.operations
|
import django.db.backends.postgresql.operations
|
||||||
import django.db.backends.postgresql.schema
|
|
||||||
import django.db.backends.signals
|
import django.db.backends.signals
|
||||||
import django.db.backends.sqlite3
|
import django.db.backends.sqlite3
|
||||||
import django.db.backends.sqlite3.base
|
import django.db.backends.sqlite3.base
|
||||||
import django.db.backends.sqlite3.client
|
|
||||||
import django.db.backends.sqlite3.creation
|
import django.db.backends.sqlite3.creation
|
||||||
import django.db.backends.sqlite3.features
|
import django.db.backends.sqlite3.features
|
||||||
import django.db.backends.sqlite3.introspection
|
import django.db.backends.sqlite3.introspection
|
||||||
import django.db.backends.sqlite3.operations
|
import django.db.backends.sqlite3.operations
|
||||||
import django.db.backends.sqlite3.schema
|
import django.db.backends.sqlite3.schema
|
||||||
import django.db.backends.utils
|
import django.db.backends.utils
|
||||||
import django.db.migrations
|
|
||||||
import django.db.migrations.autodetector
|
import django.db.migrations.autodetector
|
||||||
import django.db.migrations.exceptions
|
import django.db.migrations.exceptions
|
||||||
import django.db.migrations.executor
|
import django.db.migrations.executor
|
||||||
@@ -471,17 +265,14 @@
|
|||||||
import django.db.migrations.topological_sort
|
import django.db.migrations.topological_sort
|
||||||
import django.db.migrations.utils
|
import django.db.migrations.utils
|
||||||
import django.db.migrations.writer
|
import django.db.migrations.writer
|
||||||
import django.db.models
|
|
||||||
import django.db.models.aggregates
|
import django.db.models.aggregates
|
||||||
import django.db.models.base
|
import django.db.models.base
|
||||||
import django.db.models.constants
|
|
||||||
import django.db.models.constraints
|
import django.db.models.constraints
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import django.db.models.enums
|
|
||||||
import django.db.models.expressions
|
import django.db.models.expressions
|
||||||
|
import django.db.models.enums
|
||||||
import django.db.models.fields
|
import django.db.models.fields
|
||||||
import django.db.models.fields.files
|
import django.db.models.fields.files
|
||||||
import django.db.models.fields.json
|
|
||||||
import django.db.models.fields.mixins
|
import django.db.models.fields.mixins
|
||||||
import django.db.models.fields.proxy
|
import django.db.models.fields.proxy
|
||||||
import django.db.models.fields.related
|
import django.db.models.fields.related
|
||||||
@@ -538,7 +329,6 @@
|
|||||||
import django.middleware.locale
|
import django.middleware.locale
|
||||||
import django.middleware.security
|
import django.middleware.security
|
||||||
import django.shortcuts
|
import django.shortcuts
|
||||||
import django.template
|
|
||||||
import django.template.backends
|
import django.template.backends
|
||||||
import django.template.backends.base
|
import django.template.backends.base
|
||||||
import django.template.backends.django
|
import django.template.backends.django
|
||||||
@@ -585,10 +375,8 @@
|
|||||||
import django.urls.exceptions
|
import django.urls.exceptions
|
||||||
import django.urls.resolvers
|
import django.urls.resolvers
|
||||||
import django.urls.utils
|
import django.urls.utils
|
||||||
import django.utils
|
|
||||||
import django.utils._os
|
import django.utils._os
|
||||||
import django.utils.archive
|
import django.utils.archive
|
||||||
import django.utils.asyncio
|
|
||||||
import django.utils.autoreload
|
import django.utils.autoreload
|
||||||
import django.utils.baseconv
|
import django.utils.baseconv
|
||||||
import django.utils.cache
|
import django.utils.cache
|
||||||
@@ -633,7 +421,6 @@
|
|||||||
import django.utils.tree
|
import django.utils.tree
|
||||||
import django.utils.version
|
import django.utils.version
|
||||||
import django.utils.xmlutils
|
import django.utils.xmlutils
|
||||||
import django.views
|
|
||||||
import django.views.csrf
|
import django.views.csrf
|
||||||
import django.views.debug
|
import django.views.debug
|
||||||
import django.views.decorators
|
import django.views.decorators
|
||||||
Reference in New Issue
Block a user