55 Commits

Author SHA1 Message Date
Dave Halter
28afe59881 Model.objects should be a ClassVar
Model.objects is only available on classes and while we cannot force this, without ClassVar it's kind of only available on instances.

I have noticed this when I was writing a Jedi plugin that makes django-stubs work properly.
2021-02-26 22:15:01 +01:00
melvyn-oviavo
54e5ecc16a feat(add-get-lines): Add get_inlines method (#570)
Document ModelAdmin.get_inlines(). Available since Django 3.0.
2021-02-22 11:54:35 +03:00
Seth Yastrov
418437b00c Mention issue about as_manager along with a workaround (#569)
* Mention issue about as_manager along with a workaround

* Update README.md

Co-authored-by: Nikita Sobolev <mail@sobolevn.me>
2021-02-18 09:56:09 +03:00
Daniel
eb195b61e9 Correct order for FileField __init__ args (#568) 2021-02-15 13:38:04 +03:00
Thomas Krapp
993dd816a4 Update static.pyi (#566)
* Update static.pyi

Parameters document_root and show_indexes should be marked as Optional since they have a default value.

* Update static.pyi

Correct signature of serve function.
2021-02-13 00:27:12 +03:00
Chas Nelson
e6dcfe4fc6 fix: add headers to http.HttpResponseBase constructor (#564)
* fix: add headers to http.HttpResponseBase constructor

fixes #563

* fix: correct Dict() with Dict[]

* feat: add ResponseHeaders typing

* fix: correct HttpResponseBase.headers type

* fix: add missing ...s

* fix: add CaseInsensitiveMapping import

* fix: make Dict -> Dict[str, str]
2021-02-09 22:07:35 +03:00
Simon Charette
8f9e77ce39 Allow ModelAdmin.actions to be set to None. (#560)
`None` is an allowed value to disable all actions for a particular `ModelAdmin`.
2021-01-31 10:24:32 +03:00
Tim Martin
a1334a70b9 Stricter return type annotations for template.Library (#541)
* Stricter return type annotations for template.Library

* Add some unit tests for the template library decorators
2021-01-20 23:11:02 +03:00
Nicolas Delaby
a8d8561d0e Add deferrable argument to unique constraint (#472) 2021-01-20 11:04:38 +03:00
Yuta Okamoto
bdc7db1154 Add algorithm parameter (Django 3.1) (#489)
* add algorithm parameter

* add algorithm field

* reformat
2021-01-20 11:03:51 +03:00
Maksim Kurnikov
49ed9c957f Merge pull request #536 from wkschwartz/patch-1
Fix incorrect and add missing annotations for loaddata command
2021-01-20 02:31:04 +03:00
Maksim Kurnikov
4829ce2200 Merge pull request #510 from jpulec/patch-1
Add resolve_callables to db.models.utils (Django 3.1)
2021-01-20 02:20:35 +03:00
Maksim Kurnikov
9fe1a76779 Merge pull request #559 from mkurnikov/value-to-string
add value_to_string method to Field
2021-01-20 02:18:20 +03:00
Maksim Kurnikov
153613eddb run black 2021-01-20 02:11:37 +03:00
Maksim Kurnikov
b6cb50e729 Merge pull request #538 from bullfest/av/ModelMultipleChoiceField
Make queryset parameter to ModelMultipleChoiceField.__init__ Optional
2021-01-20 02:02:01 +03:00
Maksim Kurnikov
344dea06b5 value_to_string obj param is Model 2021-01-20 01:58:29 +03:00
Maksim Kurnikov
01a9ad12e1 Merge pull request #533 from woochica/master
Add support for PositiveBigIntegerField (Django 3.1)
2021-01-20 00:19:13 +03:00
Maksim Kurnikov
0a0e1985d7 add value_to_string method to Field 2021-01-20 00:14:15 +03:00
Maksim Kurnikov
f07477260f Merge pull request #558 from mkurnikov/jsonfield
Missing Django 3.1 items
2021-01-20 00:10:24 +03:00
Maksim Kurnikov
11c3f8cd11 add django 3.1 classproperty 2021-01-19 22:16:12 +03:00
Maksim Kurnikov
159f8b95ba add django 3.1 jsonfield 2021-01-19 22:13:36 +03:00
Anas
58c087f7f5 Added cache in github actions to speed up CI (#549) 2020-12-22 23:37:41 +03:00
markedwards
2e9adce5eb Add Field to django_stubs_ext.django_stubs_ext.patch._need_generic (#547) 2020-12-22 10:09:29 +03:00
markedwards
47bd07758b Update django_stubs_ext documentation to reference settings (#548) 2020-12-19 11:50:46 +03:00
Marti Raudsepp
36d1778e2a Django admin additions (#543) 2020-12-17 13:23:51 +03:00
proxi
caaa23ab8f convince mypy that user.is_staff (and friends) are booleans (#542)
closes #512

Co-authored-by: proxi <51172302+3n-k1@users.noreply.github.com>
2020-12-16 23:28:01 +03:00
Marcin Wieczorek
cfd9379b58 Change patch_response_headers argument to Optional[int] (Fixes #337) (#338) 2020-12-13 16:21:58 +03:00
Alexander Viklund
362342d998 Make queryset parameter to ModelMultipleChoiceField Optional 2020-12-09 17:12:54 +01:00
William Schwartz
d3ff5415db Finish loaddata.pyi
This incorporates all type information I could glean from loaddata at
django/django@adb40d217e.

- Remove `help` per review comment:
  https://github.com/typeddjango/django-stubs/pull/536#discussion_r533179013
- Add `exclude_models` and `exclude_apps` based on the return type of
  `..utils.parse_apps_and_model_labels`.
- Change `loaddata`'s `fixture_labels` to `Sequence` of `str` instead of
  `Iterable` because in practice it's a tuple, but at a type level, the
  important thing is that `loaddata` iterates over `fixture_labels` more than
  once. General iterables (which include iterators) need not support iteration
  more than once.
- Correct the return type of `parse_name` to account for the possibility that
  the data and compression formats are `None`.
- Correct the return type of `find_fixtures` to be a list of the same type that
  `parse_name` returns.
- Add a type annotation for `SingleZipReader.read`. Django implements the method
  in a way that actually conflicts with `zipfile.ZipFile.read`'s type. This
  necessitates a `type: ignore[override]` to keep the tests passing. Mypy is
  correct that there is an override error, but Django's code is what it is.
  (And that method's signature was introduced way back in Django version 1.1,
  commit django/django@089ab18c025917f38a2e3731ae4024d4810df1ec.)
2020-12-01 11:32:59 -06:00
William Schwartz
eecf13a2fe Fix loaddata.pyi
- Added some of the instance attributes from command arguments: ignore, using, app_label, verbosity, format.
- Added class attribute: help.
- Fixed return type of find_fixtures.
2020-12-01 00:42:56 -06:00
Lysandros Nikolaou
d9c851abce Do not force django.contrib.* dependencies (#535)
* Do not force django.contrib.* dependencies

Fixes #428.
Fixes #534.

* Add one more test with contenttypes installed, but auth not
2020-11-24 14:38:03 +03:00
Torok Gabor
896cbe4752 Add support for PositiveBigIntegerField (Django 3.1) 2020-11-20 08:28:13 +01:00
Alisue
f3e0872d6e Fix signature of RemoteUserBackend.configure_user (#532)
* Fix signature of RemoteUserBackend.configure_user

The function signature has changed from 2.1 on 2.2 but django-stubs did not follow that.

https://github.com/django/django/blob/2.1.15/django/contrib/auth/backends.py#L163
https://github.com/django/django/blob/2.2/django/contrib/auth/backends.py#L177

* Use HttpRequest on request to make it more explicit
2020-11-19 18:38:59 +03:00
javulticat
4cb13a6ac5 AttributeError exception in copy_method_to_another_class (#531)
* Fix for method_node having no arguments

* Trim whitespace
2020-11-17 10:24:59 +03:00
Nikita Sobolev
19d695b1da Update misspel.yml 2020-11-17 10:15:18 +03:00
sobolevn
16326e999c Fixes CI 2020-11-14 21:18:09 +03:00
sobolevn
517ae648e5 Adds more types to patch 2020-11-14 20:46:32 +03:00
sobolevn
3e0f144148 Fixes README 2020-11-14 20:05:36 +03:00
Nikita Sobolev
8a68111ce5 Closes #529 (#530)
* Closes #529

* Fixing CI

* Fixing CI

* new monorepo setup
2020-11-14 20:03:50 +03:00
sobolevn
9f966a8056 django_stubs_ext release 0.1.0 2020-11-14 19:17:38 +03:00
proxy
0c41d0c6e9 create monkeypatching function for adding get_item dunder (#526)
* run black

* create monkeypatching function for adding get_item dunder

* whoops i forgot the test

* change the name in INSTALLED_APPS to make test pass

* turn the whole thing into a proper package

* move django_stubs_ext to requirements.txt

* also install requirements.txt

* attempt to fix pre-commit

* numerous small code review fixes

* fix dependency issues

* small dependency fixes

* configure proper license file location

* add the rest of the monkeypatching

* use strict mypy

* update contributing with a note monkeypatching generics

* copy release script from parent package
2020-11-11 10:04:13 +03:00
Tim Martin
e798b496c0 Add stubs for remaining commands in django.core.management.commands (#525)
* Add stubs for remaining commands in django.core.management.commands

* Tighten up stub definitions in django.core.management.commands

* Apply some missing declarations in django.core.management.commands
2020-11-11 01:56:41 +03:00
Francesc
01af2ff588 Annotate get_asgi_application (#527) 2020-11-09 20:27:17 +03:00
proxy
aab8acf2ea change get_user to use a protocol requiring a session (#522)
* change get_user to use a protocol requiring a session
define a "_HasSession" protocol, and update contrib.auth.get_user to use it
get_user only requires a session field, and idiomatic django testing frequently calls get_user with a TestClient

* run black

* use union for get_user instead of a protocol

* create tests for get_user typechecking

* properly import test client
2020-11-07 11:38:45 +03:00
Clarity
1c4a7d25c7 Add Point and Polygon to contrib.gis.geos (#524) 2020-11-07 01:34:47 +03:00
Robert Huselius
a0d61c0de3 Change InlineModelAdmin.formset to Type[BaseInlineFormSet] (#521)
* Change InlineModelAdmin.formset to Type[BaseInlineFormSet]

* More exact annotations + add NaturalTimeFormatter

* Correction: cls type is Type[...]

* Complete annotations for PasswordResetTokenGenerator

* fix *gettext*_lazy annotations
2020-11-06 11:04:34 +03:00
Ceesjan Luiten
e837dac26a Fix ResourceWarning (#520) 2020-11-05 16:47:41 +03:00
Yanky Hoffman
bbdf15a6ec Add is_active field to django.contrib.auth.AbstractBaseUser model (#517)
* Add `is_active` field to `django.contrib.auth.AbstractBaseUser` model

* Favor defining type than setting concrete value

* Update type to reflect sub-class implementation
2020-11-03 18:07:45 +03:00
proxy
f08b428027 make FormMixin generic to allow proper typing for LoginView (#515)
closes #514
2020-10-31 21:53:45 +03:00
Na'aman Hirschfeld
44151c485d 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
2020-10-29 11:59:48 +03:00
Brian Helba
a3624dec36 Provide a more specific signature for django.core.checks.register (#509)
This also corrects the fact that "run_checks" only returns a concatenated list
of all registered check results, which may only contain CheckMessage:
302caa40e4/django/core/checks/registry.py (L60)
2020-10-29 10:31:26 +03:00
James Pulec
9a9a0123c7 Fix Generator Type 2020-10-28 21:33:43 -07:00
James Pulec
d3d854dac8 Add resolve_callables to db.models.utils 2020-10-28 20:27:46 -07:00
Brian Helba
6dc2c32382 Ensure that all registered checks take the same parameters (#499)
The Django API requires that registered checks take an "app_configs"
argument, which is a list of AppConfig. This is practically also passed by
keyword, so all parameters should be specified with a "= ..." to indicate
that they are keywords.

Django always passed "app_configs" as a list, but checks can accept and
use a more general more Sequence.

The Django API also requires that registered checks take "**kwargs".

This results in the canonical parameters of:
"app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any"
2020-10-29 02:21:32 +03:00
Brian Helba
695cdb16ca Add django.forms.widgets.FILE_INPUT_CONTRADICTION symbol (#506)
302caa40e4/django/forms/widgets.py (L399)
2020-10-29 02:20:40 +03:00
159 changed files with 2264 additions and 1720 deletions

20
.github/workflows/misspel.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: misspell
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: sobolevn/misspell-fixer-action@0.1.0
- uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'Fixes by misspell-fixer'
title: 'Typos fix by misspell-fixer'

85
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,85 @@
name: test
on: [push, pull_request, workflow_dispatch]
jobs:
lint:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9']
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-lint-${{ hashFiles('./dev-requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-${{ matrix.python-version }}-lint-
- name: Install dependencies
run: |
pip install -U pip setuptools wheel
pip install -r ./dev-requirements.txt
- name: Run pre-commit
run: pre-commit install && pre-commit run --all-files
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.7']
steps:
- uses: actions/checkout@v2
- name: Setup system dependencies
run: sudo apt-get install binutils libproj-dev gdal-bin
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-test-${{ hashFiles('./dev-requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-${{ matrix.python-version }}-test-
- name: Install dependencies
run: |
pip install -U pip setuptools wheel
pip install -r ./dev-requirements.txt
- name: Run tests
run: pytest
typecheck:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.6', '3.7', '3.8', '3.9']
django-version: ['2.2', '3.0']
steps:
- uses: actions/checkout@v2
- name: Setup system dependencies
run: sudo apt-get install binutils libproj-dev gdal-bin
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ matrix.django-version }}-typecheck-${{ hashFiles('./dev-requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-${{ matrix.python-version }}-${{ matrix.django-version }}-typecheck-
- name: Install dependencies
run: |
pip install -U pip setuptools wheel
pip install -r ./dev-requirements.txt
- name: Run tests
run: python ./scripts/typecheck_tests.py --django_version="${{ matrix.django-version }}"

17
.gitignore vendored
View File

@@ -1,15 +1,12 @@
*.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
/.direnv
django-sources/
.venv/ .venv/
__pycache__/
django-source/
out/
pip-wheel-metadata/
stubgen/
build/

46
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,46 @@
# 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/|django_stubs_ext/"
args: [ "--config=mypy.ini", "--cache-dir=/dev/null", "--no-incremental" ]
- id: mypy
name: mypy (django_stubs_ext)
entry: mypy
language: system
types: [ python ]
files: "django_stubs_ext/|django_stubs_ext/tests/"
args: [ "--config=mypy.ini", "--cache-dir=/dev/null", "--no-incremental", "--strict" ]

View File

@@ -1,61 +0,0 @@
language: python
cache: pip
dist: xenial
sudo: required
jobs:
include:
- name: Run plugin test suite with python 3.8
python: 3.8
script: 'pytest'
- 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
python: 3.8
script: |
python ./scripts/typecheck_tests.py --django_version=3.0
- name: Typecheck Django 3.0 test suite with python 3.7
python: 3.7
script: |
python ./scripts/typecheck_tests.py --django_version=3.0
- name: Typecheck Django 3.0 test suite with python 3.6
python: 3.6
script: |
python ./scripts/typecheck_tests.py --django_version=3.0
- name: Typecheck Django 2.2 test suite with python 3.7
python: 3.7
script: |
python ./scripts/typecheck_tests.py --django_version=2.2
- name: Mypy for plugin code
python: 3.7
script: 'mypy ./mypy_django_plugin'
- name: Lint with black
python: 3.7
script: 'black --check django-stubs/ setup.py'
- 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: |
sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable -y
sudo apt-get update
sudo apt-get install -y binutils libproj-dev gdal-bin
pip install -U pip setuptools wheel
install: |
pip install -r ./dev-requirements.txt

View File

@@ -1,111 +1,112 @@
# 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.
## A Note About Generics
As Django uses a lot of the more dynamic features of Python (i.e. metaobjects), statically typing it requires heavy use of generics. Unfortunately, the syntax for generics is also valid python syntax. For instance, the statement `class SomeClass(SuperType[int])` implicitly translates to `class SomeClass(SuperType.__class_getitem__(int))`. If `SuperType` doesn't define the `__class_getitem__` method, this causes a runtime error, even if the code typechecks.
When adding a new generic class, or changing an existing class to use generics, run a quick test to see if it causes a runtime error. If it does, please add the new generic class to the `_need_generic` list in the [django_stubs_ext monkeypatch function](https://github.com/typeddjango/django-stubs/tree/master/django_stubs_ext/django_stubs_ext/monkeypatch.py)

100
README.md
View File

@@ -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
[![Build Status](https://travis-ci.com/typeddjango/django-stubs.svg?branch=master)](https://travis-ci.com/typeddjango/django-stubs) [![Build status](https://github.com/typeddjango/django-stubs/workflows/test/badge.svg?branch=master&event=push)](https://github.com/typeddjango/django-stubs/actions?query=workflow%3Atest)
[![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/) [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
[![Gitter](https://badges.gitter.im/mypy-django/Lobby.svg)](https://gitter.im/mypy-django/Lobby) [![Gitter](https://badges.gitter.im/mypy-django/Lobby.svg)](https://gitter.im/mypy-django/Lobby)
This package contains [type stubs](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,12 +16,7 @@ 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]
@@ -40,13 +36,13 @@ 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:
| django-stubs | mypy version | django version | python version | django-stubs | mypy version | django version | python version
| ------------ | ---- | ---- | ---- | | ------------ | ---- | ---- | ---- |
| 1.7.0 | 0.790 | 2.2.x \|\| 3.x | ^3.6
| 1.6.0 | 0.780 | 2.2.x \|\| 3.x | ^3.6 | 1.6.0 | 0.780 | 2.2.x \|\| 3.x | ^3.6
| 1.5.0 | 0.770 | 2.2.x \|\| 3.x | ^3.6 | 1.5.0 | 0.770 | 2.2.x \|\| 3.x | ^3.6
| 1.4.0 | 0.760 | 2.2.x \|\| 3.x | ^3.6 | 1.4.0 | 0.760 | 2.2.x \|\| 3.x | ^3.6
@@ -62,7 +58,7 @@ We rely on different `django` and `mypy` versions:
No, it is not. We are independent from Django at the moment. No, it is not. We are independent from Django at the moment.
There's a [proposal](https://github.com/django/deps/pull/65) to merge our project into the Django itself. There's a [proposal](https://github.com/django/deps/pull/65) to merge our project into the Django itself.
You show your support by linking the PR. You can show your support by liking the PR.
### Is it safe to use this in production? ### Is it safe to use this in production?
@@ -73,22 +69,36 @@ 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]`, `Manager[MyModel]` or some other Django-based Generic types.
This happens because Django classes do not support [`__class_getitem__`](https://www.python.org/dev/peps/pep-0560/#class-getitem) magic method. This happens because these Django classes do not support [`__class_getitem__`](https://www.python.org/dev/peps/pep-0560/#class-getitem) magic method in runtime.
You can use strings instead: `'QuerySet[MyModel]'` and `'Manager[MyModel]'`, this way it will work as a type for `mypy` and as a regular `str` in runtime. 1. You can go with our [`django_stubs_ext`](https://github.com/typeddjango/django-stubs/tree/master/django_stubs_ext) helper, that patches all the types we use as Generic in django.
Currently we [are working](https://github.com/django/django/pull/12405) on providing `__class_getitem__` to the classes where we need them. Install it:
```bash
pip install django-stubs-ext # as a production dependency
```
And then place in your top-level settings:
```python
import django_stubs_ext
django_stubs_ext.monkeypatch()
```
2. You can use strings instead: `'QuerySet[MyModel]'` and `'Manager[MyModel]'`, this way it will work as a type for `mypy` and as a regular `str` in runtime.
### How can I create a HttpRequest that's guaranteed to have an authenticated user? ### How can I create a HttpRequest that's guaranteed to have an authenticated user?
@@ -110,6 +120,51 @@ class AuthenticatedHttpRequest(HttpRequest):
And then use `AuthenticatedHttpRequest` instead of the standard `HttpRequest` for when you know that the user is authenticated. For example in views using the `@login_required` decorator. And then use `AuthenticatedHttpRequest` instead of the standard `HttpRequest` for when you know that the user is authenticated. For example in views using the `@login_required` decorator.
### My QuerySet methods are returning Any rather than my Model
`QuerySet.as_manager()` is not currently supported.
If you are using `MyQuerySet.as_manager()`, then your `Manager`/`QuerySet` methods will all not be linked to your model.
Example:
```python
from django.db import models
class MyModelQuerySet(models.QuerySet):
pass
class MyModel(models.Model):
bar = models.IntegerField()
objects = MyModelQuerySet.as_manager()
def use_my_model():
foo = MyModel.objects.get(id=1) # This is `Any` but it should be `MyModel`
return foo.xyz # No error, but there should be
```
There is a workaround: use `Manager.from_queryset` instead.
Example:
```python
from django.db import models
class MyModelQuerySet(models.QuerySet):
pass
MyModelManager = models.Manager.from_queryset(MyModelQuerySet)
class MyModel(models.Model):
bar = models.IntegerField()
objects = MyModelManager()
def use_my_model():
foo = MyModel.objects.get(id=1)
return foo.xyz # Gives an error
```
## Related projects ## Related projects
- [`awesome-python-typing`](https://github.com/typeddjango/awesome-python-typing) - Awesome list of all typing-related things in Python. - [`awesome-python-typing`](https://github.com/typeddjango/awesome-python-typing) - Awesome list of all typing-related things in Python.
@@ -122,5 +177,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!

View File

@@ -1,8 +1,12 @@
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 -e ./django_stubs_ext
flake8-pyi==19.3.0
isort==4.3.21
gitpython==3.1.0
-e . -e .

1
django-sources Submodule

Submodule django-sources added at aa28213eb5

View File

@@ -1,4 +1,4 @@
from typing import Any, List, Union, Iterable, Optional from typing import Any, List, Union, Optional, Sequence
from django.contrib.admin.options import BaseModelAdmin from django.contrib.admin.options import BaseModelAdmin
from django.core.checks.messages import CheckMessage, Error from django.core.checks.messages import CheckMessage, Error
@@ -7,7 +7,7 @@ from django.apps.config import AppConfig
_CheckError = Union[str, Error] _CheckError = Union[str, Error]
def check_admin_app(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[_CheckError]: ... def check_admin_app(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[_CheckError]: ...
def check_dependencies(**kwargs: Any) -> List[_CheckError]: ... def check_dependencies(**kwargs: Any) -> List[_CheckError]: ...
class BaseModelAdminChecks: class BaseModelAdminChecks:

View File

@@ -1,5 +1,6 @@
from typing import Any, Callable, Optional, Type from typing import Any, Callable, Optional, Type
from django.contrib.admin.sites import AdminSite
from django.db.models.base import Model from django.db.models.base import Model
def register(*models: Type[Model], site: Optional[Any] = ...) -> Callable: ... def register(*models: Type[Model], site: Optional[AdminSite] = ...) -> Callable: ...

View File

@@ -17,7 +17,7 @@ from typing import (
) )
from django.forms.forms import BaseForm from django.forms.forms import BaseForm
from django.forms.formsets import BaseFormSet from django.forms.models import BaseInlineFormSet
from typing_extensions import Literal, TypedDict from typing_extensions import Literal, TypedDict
from django.contrib.admin.filters import ListFilter from django.contrib.admin.filters import ListFilter
@@ -154,7 +154,7 @@ class ModelAdmin(BaseModelAdmin[_ModelT]):
delete_selected_confirmation_template: str = ... delete_selected_confirmation_template: str = ...
object_history_template: str = ... object_history_template: str = ...
popup_response_template: str = ... popup_response_template: str = ...
actions: Sequence[Union[Callable[[ModelAdmin, HttpRequest, QuerySet], None], str]] = ... actions: Optional[Sequence[Union[Callable[[ModelAdmin, HttpRequest, QuerySet], None], str]]] = ...
action_form: Any = ... action_form: Any = ...
actions_on_top: bool = ... actions_on_top: bool = ...
actions_on_bottom: bool = ... actions_on_bottom: bool = ...
@@ -163,6 +163,7 @@ class ModelAdmin(BaseModelAdmin[_ModelT]):
opts: Options = ... opts: Options = ...
admin_site: AdminSite = ... admin_site: AdminSite = ...
def __init__(self, model: Type[_ModelT], admin_site: Optional[AdminSite]) -> None: ... def __init__(self, model: Type[_ModelT], admin_site: Optional[AdminSite]) -> None: ...
def get_inlines(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> List[Type[InlineModelAdmin]]: ...
def get_inline_instances(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> List[InlineModelAdmin]: ... def get_inline_instances(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> List[InlineModelAdmin]: ...
def get_urls(self) -> List[URLPattern]: ... def get_urls(self) -> List[URLPattern]: ...
@property @property
@@ -268,7 +269,7 @@ class ModelAdmin(BaseModelAdmin[_ModelT]):
class InlineModelAdmin(BaseModelAdmin[_ModelT]): class InlineModelAdmin(BaseModelAdmin[_ModelT]):
model: Type[_ModelT] = ... model: Type[_ModelT] = ...
fk_name: str = ... fk_name: str = ...
formset: BaseFormSet = ... formset: Type[BaseInlineFormSet] = ...
extra: int = ... extra: int = ...
min_num: Optional[int] = ... min_num: Optional[int] = ...
max_num: Optional[int] = ... max_num: Optional[int] = ...

View File

@@ -1,36 +1,51 @@
import sys
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type, Union from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type, Union
from django.apps.config import AppConfig
from django.contrib.admin.options import ModelAdmin from django.contrib.admin.options import ModelAdmin
from django.contrib.auth.forms import AuthenticationForm
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
from django.db.models.base import Model from django.db.models.base import Model
from django.db.models.query import QuerySet
from django.http.response import HttpResponse from django.http.response import HttpResponse
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.urls.resolvers import URLResolver from django.urls.resolvers import URLResolver
from django.utils.functional import LazyObject from django.utils.functional import LazyObject
from django.core.checks import CheckMessage
from django.apps.config import AppConfig if sys.version_info >= (3, 9):
from weakref import WeakSet
all_sites: Any all_sites: WeakSet[AdminSite]
else:
from typing import MutableSet
all_sites: MutableSet[AdminSite]
_ActionCallback = Callable[[ModelAdmin, WSGIRequest, QuerySet], Optional[TemplateResponse]]
class AlreadyRegistered(Exception): ... class AlreadyRegistered(Exception): ...
class NotRegistered(Exception): ... class NotRegistered(Exception): ...
class AdminSite: class AdminSite:
site_title: Any = ... site_title: str = ...
site_header: Any = ... site_header: str = ...
index_title: Any = ... index_title: str = ...
site_url: str = ... site_url: str = ...
login_form: Any = ... login_form: Optional[AuthenticationForm] = ...
index_template: Any = ... index_template: Optional[str] = ...
app_index_template: Any = ... app_index_template: Optional[str] = ...
login_template: Any = ... login_template: Optional[str] = ...
logout_template: Any = ... logout_template: Optional[str] = ...
password_change_template: Any = ... password_change_template: Optional[str] = ...
password_change_done_template: Any = ... password_change_done_template: Optional[str] = ...
name: str = ... name: str = ...
_empty_value_display: str = ...
_registry: Dict[Type[Model], ModelAdmin] _registry: Dict[Type[Model], ModelAdmin]
_global_actions: Dict[str, _ActionCallback]
_actions: Dict[str, _ActionCallback]
def __init__(self, name: str = ...) -> None: ... def __init__(self, name: str = ...) -> None: ...
def check(self, app_configs: Optional[Iterable[AppConfig]]) -> List[Any]: ... def check(self, app_configs: Optional[Iterable[AppConfig]]) -> List[CheckMessage]: ...
def register( def register(
self, self,
model_or_iterable: Union[Type[Model], Iterable[Type[Model]]], model_or_iterable: Union[Type[Model], Iterable[Type[Model]]],
@@ -39,28 +54,28 @@ class AdminSite:
) -> None: ... ) -> None: ...
def unregister(self, model_or_iterable: Union[Type[Model], Iterable[Type[Model]]]) -> None: ... def unregister(self, model_or_iterable: Union[Type[Model], Iterable[Type[Model]]]) -> None: ...
def is_registered(self, model: Type[Model]) -> bool: ... def is_registered(self, model: Type[Model]) -> bool: ...
def add_action(self, action: Callable, name: Optional[str] = ...) -> None: ... def add_action(self, action: _ActionCallback, name: Optional[str] = ...) -> None: ...
def disable_action(self, name: str) -> None: ... def disable_action(self, name: str) -> None: ...
def get_action(self, name: str) -> Callable: ... def get_action(self, name: str) -> Callable: ...
@property @property
def actions(self): ... def actions(self) -> Iterable[Tuple[str, _ActionCallback]]: ...
@property @property
def empty_value_display(self): ... def empty_value_display(self) -> str: ...
@empty_value_display.setter @empty_value_display.setter
def empty_value_display(self, empty_value_display: Any) -> None: ... def empty_value_display(self, empty_value_display: str) -> None: ...
def has_permission(self, request: WSGIRequest) -> bool: ... def has_permission(self, request: WSGIRequest) -> bool: ...
def admin_view(self, view: Callable, cacheable: bool = ...) -> Callable: ... def admin_view(self, view: Callable, cacheable: bool = ...) -> Callable: ...
def get_urls(self) -> List[URLResolver]: ... def get_urls(self) -> List[URLResolver]: ...
@property @property
def urls(self) -> Tuple[List[URLResolver], str, str]: ... def urls(self) -> Tuple[List[URLResolver], str, str]: ...
def each_context(self, request: Any): ... def each_context(self, request: WSGIRequest) -> Dict[str, Any]: ...
def password_change( def password_change(
self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ... self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...
) -> TemplateResponse: ... ) -> TemplateResponse: ...
def password_change_done( def password_change_done(
self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ... self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...
) -> TemplateResponse: ... ) -> TemplateResponse: ...
def i18n_javascript(self, request: WSGIRequest, extra_context: Optional[Dict[Any, Any]] = ...) -> HttpResponse: ... def i18n_javascript(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> HttpResponse: ...
def logout(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> TemplateResponse: ... def logout(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> TemplateResponse: ...
def login(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> HttpResponse: ... def login(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> HttpResponse: ...
def _build_app_dict(self, request: WSGIRequest, label: Optional[str] = ...) -> Dict[str, Any]: ... def _build_app_dict(self, request: WSGIRequest, label: Optional[str] = ...) -> Dict[str, Any]: ...
@@ -72,4 +87,4 @@ class AdminSite:
class DefaultAdminSite(LazyObject): ... class DefaultAdminSite(LazyObject): ...
site: Any site: AdminSite

View File

@@ -7,12 +7,11 @@ from django.core.handlers.wsgi import WSGIRequest
from django.db.models.base import Model from django.db.models.base import Model
from django.db.models.options import Options from django.db.models.options import Options
from django.http.request import HttpRequest from django.http.request import HttpRequest
from django.test.client import Client
from .signals import ( from .signals import user_logged_in as user_logged_in
user_logged_in as user_logged_in, from .signals import user_logged_out as user_logged_out
user_logged_out as user_logged_out, from .signals import user_login_failed as user_login_failed
user_login_failed as user_login_failed,
)
SESSION_KEY: str SESSION_KEY: str
BACKEND_SESSION_KEY: str BACKEND_SESSION_KEY: str
@@ -27,7 +26,7 @@ def login(
) -> None: ... ) -> None: ...
def logout(request: HttpRequest) -> None: ... def logout(request: HttpRequest) -> None: ...
def get_user_model() -> Type[Model]: ... def get_user_model() -> Type[Model]: ...
def get_user(request: HttpRequest) -> Union[AbstractBaseUser, AnonymousUser]: ... def get_user(request: Union[HttpRequest, Client]) -> Union[AbstractBaseUser, AnonymousUser]: ...
def get_permission_codename(action: str, opts: Options) -> str: ... def get_permission_codename(action: str, opts: Options) -> str: ...
def update_session_auth_hash(request: HttpRequest, user: AbstractBaseUser) -> None: ... def update_session_auth_hash(request: HttpRequest, user: AbstractBaseUser) -> None: ...

View File

@@ -4,6 +4,7 @@ from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import AnonymousUser, User, Permission from django.contrib.auth.models import AnonymousUser, User, Permission
from django.db.models.base import Model from django.db.models.base import Model
from django.http.request import HttpRequest
_AnyUser = Union[Model, AnonymousUser] _AnyUser = Union[Model, AnonymousUser]
@@ -11,7 +12,7 @@ UserModel: Any
class BaseBackend: class BaseBackend:
def authenticate( def authenticate(
self, request: Any, username: Optional[str] = ..., password: Optional[str] = ..., **kwargs: Any self, request: HttpRequest, username: Optional[str] = ..., password: Optional[str] = ..., **kwargs: Any
) -> Optional[AbstractBaseUser]: ... ) -> Optional[AbstractBaseUser]: ...
def get_user(self, user_id: int) -> Optional[AbstractBaseUser]: ... def get_user(self, user_id: int) -> Optional[AbstractBaseUser]: ...
def get_user_permissions(self, user_obj: _AnyUser, obj: Optional[Model] = ...) -> Set[str]: ... def get_user_permissions(self, user_obj: _AnyUser, obj: Optional[Model] = ...) -> Set[str]: ...
@@ -35,6 +36,6 @@ class AllowAllUsersModelBackend(ModelBackend): ...
class RemoteUserBackend(ModelBackend): class RemoteUserBackend(ModelBackend):
create_unknown_user: bool = ... create_unknown_user: bool = ...
def clean_username(self, username: str) -> str: ... def clean_username(self, username: str) -> str: ...
def configure_user(self, user: User) -> User: ... def configure_user(self, request: HttpRequest, user: User) -> User: ...
class AllowAllUsersRemoteUserBackend(RemoteUserBackend): ... class AllowAllUsersRemoteUserBackend(RemoteUserBackend): ...

View File

@@ -1,8 +1,9 @@
import sys import sys
from typing import Any, Optional, Tuple, List, overload, TypeVar from typing import Any, Optional, Tuple, List, overload, TypeVar, Union
from django.db.models.base import Model from django.db.models.base import Model
from django.db.models.expressions import Combinable
from django.db.models.fields import BooleanField
from django.db import models from django.db import models
if sys.version_info < (3, 8): if sys.version_info < (3, 8):
@@ -23,6 +24,7 @@ class AbstractBaseUser(models.Model):
password = models.CharField(max_length=128) password = models.CharField(max_length=128)
last_login = models.DateTimeField(blank=True, null=True) last_login = models.DateTimeField(blank=True, null=True)
is_active: Union[bool, BooleanField[Union[bool, Combinable], bool]] = ...
def get_username(self) -> str: ... def get_username(self) -> str: ...
def natural_key(self) -> Tuple[str]: ... def natural_key(self) -> Tuple[str]: ...
@property @property

View File

@@ -1,8 +1,8 @@
from typing import Any, List, Iterable, Optional from typing import Any, List, Optional, Sequence
from django.core.checks.messages import CheckMessage from django.core.checks.messages import CheckMessage
from django.apps.config import AppConfig from django.apps.config import AppConfig
def check_user_model(app_configs: Optional[Iterable[AppConfig]] = ..., **kwargs: Any) -> List[CheckMessage]: ... def check_user_model(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[CheckMessage]: ...
def check_models_permissions(app_configs: Optional[Iterable[AppConfig]] = ..., **kwargs: Any) -> List[Any]: ... def check_models_permissions(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Any]: ...

View File

@@ -1,3 +1,4 @@
from datetime import date
from typing import Any, Optional from typing import Any, Optional
from django.contrib.auth.base_user import AbstractBaseUser from django.contrib.auth.base_user import AbstractBaseUser
@@ -5,7 +6,12 @@ from django.contrib.auth.base_user import AbstractBaseUser
class PasswordResetTokenGenerator: class PasswordResetTokenGenerator:
key_salt: str = ... key_salt: str = ...
secret: Any = ... secret: Any = ...
algorithm: str = ...
def make_token(self, user: AbstractBaseUser) -> str: ... def make_token(self, user: AbstractBaseUser) -> str: ...
def check_token(self, user: Optional[AbstractBaseUser], token: Optional[str]) -> bool: ... def check_token(self, user: Optional[AbstractBaseUser], token: Optional[str]) -> bool: ...
def _make_token_with_timestamp(self, user: AbstractBaseUser, timestamp: int) -> str: ...
def _make_hash_value(self, user: AbstractBaseUser, timestamp: int) -> str: ...
def _num_days(self, dt: date) -> float: ...
def _today(self) -> date: ...
default_token_generator: Any default_token_generator: Any

View File

@@ -1,6 +1,7 @@
from typing import Any, Optional, Set from typing import Any, Optional, Set
from django.contrib.auth.base_user import AbstractBaseUser from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.forms import AuthenticationForm
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
from django.http.request import HttpRequest from django.http.request import HttpRequest
from django.http.response import HttpResponseRedirect from django.http.response import HttpResponseRedirect
@@ -14,7 +15,7 @@ class SuccessURLAllowedHostsMixin:
success_url_allowed_hosts: Any = ... success_url_allowed_hosts: Any = ...
def get_success_url_allowed_hosts(self) -> Set[str]: ... def get_success_url_allowed_hosts(self) -> Set[str]: ...
class LoginView(SuccessURLAllowedHostsMixin, FormView): class LoginView(SuccessURLAllowedHostsMixin, FormView[AuthenticationForm]):
authentication_form: Any = ... authentication_form: Any = ...
redirect_field_name: Any = ... redirect_field_name: Any = ...
redirect_authenticated_user: bool = ... redirect_authenticated_user: bool = ...

View File

@@ -1,6 +1,6 @@
from typing import Any, List, Iterable, Optional from typing import Any, List, Optional, Sequence
from django.apps.config import AppConfig from django.apps.config import AppConfig
def check_generic_foreign_keys(app_configs: Optional[Iterable[AppConfig]] = ..., **kwargs: Any) -> List[Any]: ... def check_generic_foreign_keys(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Any]: ...
def check_model_name_lengths(app_configs: Optional[Iterable[AppConfig]] = ..., **kwargs: Any) -> List[Any]: ... def check_model_name_lengths(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Any]: ...

View File

@@ -83,7 +83,6 @@ class GenericRelation(ForeignObject):
def resolve_related_fields(self) -> List[Tuple[PositiveIntegerField, Field]]: ... def resolve_related_fields(self) -> List[Tuple[PositiveIntegerField, Field]]: ...
def get_path_info(self, filtered_relation: Optional[FilteredRelation] = ...) -> List[PathInfo]: ... def get_path_info(self, filtered_relation: Optional[FilteredRelation] = ...) -> List[PathInfo]: ...
def get_reverse_path_info(self, filtered_relation: None = ...) -> List[PathInfo]: ... def get_reverse_path_info(self, filtered_relation: None = ...) -> List[PathInfo]: ...
def value_to_string(self, obj: Model) -> str: ...
def get_content_type(self) -> ContentType: ... def get_content_type(self) -> ContentType: ...
def get_extra_restriction( def get_extra_restriction(
self, where_class: Type[WhereNode], alias: Optional[str], remote_alias: str self, where_class: Type[WhereNode], alias: Optional[str], remote_alias: str

View File

@@ -8,3 +8,5 @@ from .factory import fromfile as fromfile, fromstr as fromstr
from .geometry import GEOSGeometry as GEOSGeometry, hex_regex as hex_regex, wkt_regex as wkt_regex from .geometry import GEOSGeometry as GEOSGeometry, hex_regex as hex_regex, wkt_regex as wkt_regex
from .io import WKBReader as WKBReader, WKBWriter as WKBWriter, WKTReader as WKTReader, WKTWriter as WKTWriter from .io import WKBReader as WKBReader, WKBWriter as WKBWriter, WKTReader as WKTReader, WKTWriter as WKTWriter
from .linestring import LineString as LineString, LinearRing as LinearRing from .linestring import LineString as LineString, LinearRing as LinearRing
from .point import Point as Point
from .polygon import Polygon as Polygon

View File

@@ -1,14 +1,22 @@
from datetime import date, datetime as datetime from datetime import date, datetime as datetime
from typing import Any, Optional, SupportsInt, Union from typing import Any, Callable, Dict, Optional, SupportsInt, Tuple, Type, Union
from django import template
register: Any register: template.Library
def ordinal(value: Optional[Union[str, SupportsInt]]) -> Optional[str]: ... def ordinal(value: Optional[Union[str, SupportsInt]]) -> Optional[str]: ...
def intcomma(value: Optional[Union[str, SupportsInt]], use_l10n: bool = ...) -> str: ... def intcomma(value: Optional[Union[str, SupportsInt]], use_l10n: bool = ...) -> str: ...
intword_converters: Any intword_converters: Tuple[Tuple[int, Callable]]
def intword(value: Optional[Union[str, SupportsInt]]) -> Optional[Union[int, str]]: ... def intword(value: Optional[Union[str, SupportsInt]]) -> Optional[Union[int, str]]: ...
def apnumber(value: Optional[Union[str, SupportsInt]]) -> Optional[Union[int, str]]: ... def apnumber(value: Optional[Union[str, SupportsInt]]) -> Optional[Union[int, str]]: ...
def naturalday(value: Optional[Union[date, str]], arg: None = ...) -> Optional[str]: ... def naturalday(value: Optional[Union[date, str]], arg: None = ...) -> Optional[str]: ...
def naturaltime(value: datetime) -> str: ... def naturaltime(value: datetime) -> str: ...
class NaturalTimeFormatter:
time_strings: Dict[str, str]
past_substrings: Dict[str, str]
future_substrings: Dict[str, str]
@classmethod
def string_for(cls: Type[NaturalTimeFormatter], value: Any) -> Any: ...

View File

@@ -22,7 +22,6 @@ class JSONField(CheckFieldDefaultMixin, Field):
encoder: Optional[Type[JSONEncoder]] = ..., encoder: Optional[Type[JSONEncoder]] = ...,
**kwargs: Any **kwargs: Any
) -> None: ... ) -> None: ...
def value_to_string(self, obj: Any): ...
class KeyTransform(Transform): class KeyTransform(Transform):
operator: str = ... operator: str = ...

View File

@@ -10,7 +10,6 @@ class RangeField(models.Field):
range_type: Any = ... range_type: Any = ...
def get_prep_value(self, value: Any): ... def get_prep_value(self, value: Any): ...
def to_python(self, value: Any): ... def to_python(self, value: Any): ...
def value_to_string(self, obj: Any): ...
class IntegerRangeField(RangeField): class IntegerRangeField(RangeField):
def __get__(self, instance, owner) -> NumericRange: ... def __get__(self, instance, owner) -> NumericRange: ...

View File

@@ -1,7 +1,7 @@
from typing import Any, List, Iterable, Optional from typing import Any, List, Optional, Sequence
from django.core.checks.messages import Error from django.core.checks.messages import Error
from django.apps.config import AppConfig from django.apps.config import AppConfig
def check_finders(app_configs: Optional[Iterable[AppConfig]] = ..., **kwargs: Any) -> List[Error]: ... def check_finders(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Error]: ...

View File

@@ -1 +1,3 @@
def get_asgi_application(): ... from django.core.handlers.asgi import ASGIHandler
def get_asgi_application() -> ASGIHandler: ...

View File

@@ -1,5 +1,7 @@
from typing import Any from typing import Any, Optional, Sequence
from django.apps.config import AppConfig
E001: Any E001: Any
def check_async_unsafe(app_configs: Any, **kwargs: Any): ... def check_async_unsafe(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any): ...

View File

@@ -1,4 +1,4 @@
from typing import Any, List, Iterable, Optional from typing import Any, List, Optional, Sequence
from django.core.checks.messages import Error from django.core.checks.messages import Error
@@ -6,4 +6,6 @@ from django.apps.config import AppConfig
E001: Any E001: Any
def check_default_cache_is_configured(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Error]: ... def check_default_cache_is_configured(
app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any
) -> List[Error]: ...

View File

@@ -1,8 +1,8 @@
from typing import Any, List, Iterable, Optional from typing import Any, List, Optional, Sequence
from django.core.checks.messages import Warning from django.core.checks.messages import Warning
from django.apps.config import AppConfig from django.apps.config import AppConfig
def check_all_models(app_configs: Optional[Iterable[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ... def check_all_models(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...
def check_lazy_references(app_configs: Optional[Iterable[AppConfig]] = ..., **kwargs: Any) -> List[Any]: ... def check_lazy_references(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Any]: ...

View File

@@ -15,17 +15,19 @@ class Tags:
translation: str = ... translation: str = ...
urls: str = ... urls: str = ...
_CheckCallable = Callable[..., List[CheckMessage]]
class CheckRegistry: class CheckRegistry:
registered_checks: Set[Callable] = ... registered_checks: Set[Callable] = ...
deployment_checks: Set[Callable] = ... deployment_checks: Set[Callable] = ...
def __init__(self) -> None: ... def __init__(self) -> None: ...
def register(self, check: Optional[Union[Callable, str]] = ..., *tags: Any, **kwargs: Any) -> Callable: ... def register(self, check: Optional[Union[_CheckCallable, str]] = ..., *tags: str, **kwargs: Any) -> Callable: ...
def run_checks( def run_checks(
self, self,
app_configs: Optional[List[AppConfig]] = ..., app_configs: Optional[List[AppConfig]] = ...,
tags: Optional[List[str]] = ..., tags: Optional[List[str]] = ...,
include_deployment_checks: bool = ..., include_deployment_checks: bool = ...,
) -> Union[List[CheckMessage], List[int], List[str]]: ... ) -> List[CheckMessage]: ...
def tag_exists(self, tag: str, include_deployment_checks: bool = ...) -> bool: ... def tag_exists(self, tag: str, include_deployment_checks: bool = ...) -> bool: ...
def tags_available(self, deployment_checks: bool = ...) -> Set[str]: ... def tags_available(self, deployment_checks: bool = ...) -> Set[str]: ...
def get_checks(self, include_deployment_checks: bool = ...) -> List[Callable]: ... def get_checks(self, include_deployment_checks: bool = ...) -> List[Callable]: ...

View File

@@ -1,4 +1,4 @@
from typing import Any, List, Iterable, Optional from typing import Any, List, Optional, Sequence
from django.core.checks.messages import Warning from django.core.checks.messages import Warning
@@ -19,15 +19,17 @@ W019: Any
W020: Any W020: Any
W021: Any W021: Any
def check_security_middleware(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... def check_security_middleware(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...
def check_xframe_options_middleware(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... def check_xframe_options_middleware(
def check_sts(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any
def check_sts_include_subdomains(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... ) -> List[Warning]: ...
def check_sts_preload(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... def check_sts(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...
def check_content_type_nosniff(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... def check_sts_include_subdomains(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...
def check_xss_filter(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... def check_sts_preload(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...
def check_ssl_redirect(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... def check_content_type_nosniff(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...
def check_secret_key(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... def check_xss_filter(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...
def check_debug(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... def check_ssl_redirect(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...
def check_xframe_deny(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... def check_secret_key(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...
def check_allowed_hosts(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... def check_debug(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...
def check_xframe_deny(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...
def check_allowed_hosts(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...

View File

@@ -1,11 +1,10 @@
from typing import Any, List, Iterable, Optional from typing import Any, List, Optional, Sequence
from django.core.checks.messages import Warning
from django.apps.config import AppConfig from django.apps.config import AppConfig
from django.core.checks.messages import Warning
W003: Any W003: Any
W016: Any W016: Any
def check_csrf_middleware(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... def check_csrf_middleware(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...
def check_csrf_cookie_secure(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... def check_csrf_cookie_secure(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...

View File

@@ -1,4 +1,4 @@
from typing import Any, List, Iterable, Optional from typing import Any, List, Optional, Sequence
from django.core.checks.messages import Warning from django.core.checks.messages import Warning
@@ -16,5 +16,5 @@ W013: Any
W014: Any W014: Any
W015: Any W015: Any
def check_session_cookie_secure(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... def check_session_cookie_secure(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...
def check_session_cookie_httponly(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... def check_session_cookie_httponly(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...

View File

@@ -1,4 +1,4 @@
from typing import Any, List, Iterable, Optional from typing import Any, List, Optional, Sequence
from django.core.checks.messages import Error from django.core.checks.messages import Error
@@ -7,5 +7,7 @@ from django.apps.config import AppConfig
E001: Any E001: Any
E002: Any E002: Any
def check_setting_app_dirs_loaders(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Error]: ... def check_setting_app_dirs_loaders(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Error]: ...
def check_string_if_invalid_is_string(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Error]: ... def check_string_if_invalid_is_string(
app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any
) -> List[Error]: ...

View File

@@ -1,7 +1,9 @@
from typing import Any, List from typing import Any, List, Optional, Sequence
from django.apps.config import AppConfig
from . import Error from . import Error
E001: Error = ... E001: Error = ...
def check_setting_language_code(app_configs: Any, **kwargs: Any) -> List[Error]: ... def check_setting_language_code(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Error]: ...

View File

@@ -1,13 +1,13 @@
from typing import Any, Callable, List, Tuple, Union, Iterable, Optional from typing import Any, Callable, List, Tuple, Union, Optional, Sequence
from django.core.checks.messages import CheckMessage, Error, Warning from django.core.checks.messages import CheckMessage, Error, Warning
from django.urls.resolvers import URLPattern, URLResolver from django.urls.resolvers import URLPattern, URLResolver
from django.apps.config import AppConfig from django.apps.config import AppConfig
def check_url_config(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[CheckMessage]: ... def check_url_config(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[CheckMessage]: ...
def check_resolver(resolver: Union[Tuple[str, Callable], URLPattern, URLResolver]) -> List[CheckMessage]: ... def check_resolver(resolver: Union[Tuple[str, Callable], URLPattern, URLResolver]) -> List[CheckMessage]: ...
def check_url_namespaces_unique(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Warning]: ... def check_url_namespaces_unique(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Warning]: ...
def get_warning_for_invalid_pattern(pattern: Any) -> List[Error]: ... def get_warning_for_invalid_pattern(pattern: Any) -> List[Error]: ...
def check_url_settings(app_configs: Optional[Iterable[AppConfig]], **kwargs: Any) -> List[Error]: ... def check_url_settings(app_configs: Optional[Sequence[AppConfig]] = ..., **kwargs: Any) -> List[Error]: ...
def E006(name: str) -> Error: ... def E006(name: str) -> Error: ...

View File

@@ -0,0 +1,8 @@
from django.apps import apps as apps
from django.core import checks as checks
from django.core.checks.registry import registry as registry
from django.core.management.base import BaseCommand as BaseCommand, CommandError as CommandError
from typing import Any, List
class Command(BaseCommand):
def handle(self, *app_labels: List[str], **options: Any) -> None: ...

View File

@@ -0,0 +1,20 @@
import os
from django.core.management.base import (
BaseCommand as BaseCommand,
CommandError as CommandError,
CommandParser as CommandParser,
)
from django.core.management.utils import find_command as find_command, popen_wrapper as popen_wrapper
from typing import List, Tuple, Union
_PathType = Union[str, bytes, os.PathLike]
def has_bom(fn: _PathType) -> bool: ...
def is_writable(path: _PathType) -> bool: ...
class Command(BaseCommand):
program: str = ...
program_options: List[str] = ...
verbosity: int = ...
has_errors: bool = ...
def compile_messages(self, locations: List[Tuple[_PathType, _PathType]]) -> None: ...

View File

@@ -0,0 +1,18 @@
from django.conf import settings as settings
from django.core.cache import caches as caches
from django.core.cache.backends.db import BaseDatabaseCache as BaseDatabaseCache
from django.core.management.base import BaseCommand as BaseCommand, CommandError as CommandError
from django.db import (
DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS,
DatabaseError as DatabaseError,
connections as connections,
models as models,
router as router,
transaction as transaction,
)
from typing import Any, List
class Command(BaseCommand):
verbosity: int = ...
def handle(self, *tablenames: List[str], **options: Any) -> None: ...
def create_table(self, database: str, tablename: str, dry_run: bool) -> None: ...

View File

@@ -0,0 +1,8 @@
from django.core.management.base import (
BaseCommand as BaseCommand,
CommandError as CommandError,
CommandParser as CommandParser,
)
from django.db import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS, connections as connections
class Command(BaseCommand): ...

View File

@@ -0,0 +1,12 @@
from django.core.management.base import BaseCommand as BaseCommand
from typing import Any, Callable, Dict, List
def module_to_dict(module: Any, omittable: Callable[[str], bool] = ...) -> Dict[str, str]: ...
class Command(BaseCommand):
def output_hash(
self, user_settings: Dict[str, str], default_settings: Dict[str, str], **options: Any
) -> List[str]: ...
def output_unified(
self, user_settings: Dict[str, str], default_settings: Dict[str, str], **options: Any
) -> List[str]: ...

View File

@@ -0,0 +1,10 @@
from django.apps import apps as apps
from django.core.management.base import BaseCommand as BaseCommand, CommandError as CommandError
from django.core.management.color import Style, no_style as no_style
from django.core.management.sql import emit_post_migrate_signal as emit_post_migrate_signal, sql_flush as sql_flush
from django.db import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS, connections as connections
from typing import Tuple
class Command(BaseCommand):
stealth_options: Tuple[str] = ...
style: Style = ...

View File

@@ -0,0 +1,16 @@
from django.core.management.base import BaseCommand as BaseCommand, CommandError as CommandError
from django.db import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS, connections as connections
from django.db.models.constants import LOOKUP_SEP as LOOKUP_SEP
from typing import Any, Dict, Iterable, List, Tuple
class Command(BaseCommand):
stealth_options: Tuple[str] = ...
db_module: str = ...
def handle_inspection(self, options: Dict[str, Any]) -> Iterable[str]: ...
def normalize_col_name(
self, col_name: str, used_column_names: List[str], is_relation: bool
) -> Tuple[str, Dict[str, str], List[str]]: ...
def get_field_type(self, connection: Any, table_name: Any, row: Any) -> Tuple[str, Dict[str, str], List[str]]: ...
def get_meta(
self, table_name: str, constraints: Any, column_to_field_name: Any, is_view: Any, is_partition: Any
) -> List[str]: ...

View File

@@ -1,19 +1,35 @@
import zipfile import zipfile
from typing import Iterable, List, Optional, Tuple from typing import List, Optional, Set, Sequence, Tuple, Type
from django.apps.config import AppConfig
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.db.models.base import Model
READ_STDIN: str = ... READ_STDIN: str = ...
class Command(BaseCommand): class Command(BaseCommand):
ignore: bool = ...
using: str = ...
app_label: str = ...
verbosity: int = ...
excluded_models: Set[Type[Model]] = ...
excluded_apps: Set[AppConfig] = ...
format: str = ...
missing_args_message: str = ... missing_args_message: str = ...
def loaddata(self, fixture_labels: Iterable[str]) -> None: ... def loaddata(self, fixture_labels: Sequence[str]) -> None: ...
def load_label(self, fixture_label: str) -> None: ... def load_label(self, fixture_label: str) -> None: ...
def find_fixtures(self, fixture_label: str) -> List[Optional[str]]: ... def find_fixtures(self, fixture_label: str) -> List[Tuple[str, Optional[str], Optional[str]]]: ...
@property @property
def fixture_dirs(self) -> List[str]: ... def fixture_dirs(self) -> List[str]: ...
def parse_name(self, fixture_name: str) -> Tuple[str, str, str]: ... def parse_name(self, fixture_name: str) -> Tuple[str, Optional[str], Optional[str]]: ...
class SingleZipReader(zipfile.ZipFile): ... class SingleZipReader(zipfile.ZipFile):
# Incompatible override
# zipfile.ZipFile.read(
# self,
# name: typing.Union[typing.Text, zipfile.ZipInfo],
# pwd: Optional[bytes] = ...,
# ) -> bytes: ...
def read(self) -> bytes: ... # type: ignore[override]
def humanize(dirname: str) -> str: ... def humanize(dirname: str) -> str: ...

View File

@@ -0,0 +1,36 @@
from django.apps import apps as apps
from django.conf import settings as settings
from django.core.management.base import (
BaseCommand as BaseCommand,
CommandError as CommandError,
no_translations as no_translations,
)
from django.db import (
DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS,
OperationalError as OperationalError,
connections as connections,
router as router,
)
from django.db.migrations import Migration as Migration
from django.db.migrations.autodetector import MigrationAutodetector as MigrationAutodetector
from django.db.migrations.loader import MigrationLoader as MigrationLoader
from django.db.migrations.questioner import (
InteractiveMigrationQuestioner as InteractiveMigrationQuestioner,
MigrationQuestioner as MigrationQuestioner,
NonInteractiveMigrationQuestioner as NonInteractiveMigrationQuestioner,
)
from django.db.migrations.state import ProjectState as ProjectState
from django.db.migrations.utils import get_migration_name_timestamp as get_migration_name_timestamp
from django.db.migrations.writer import MigrationWriter as MigrationWriter
from typing import Any, Dict
class Command(BaseCommand):
verbosity: int = ...
interactive: bool = ...
dry_run: bool = ...
merge: bool = ...
empty: bool = ...
migration_name: str = ...
include_header: bool = ...
def write_migration_files(self, changes: Dict[str, Any]) -> None: ...
def handle_merge(self, loader: MigrationLoader, conflicts: Dict[str, Any]) -> None: ...

View File

@@ -0,0 +1,28 @@
from django.apps import apps as apps
from django.core.management.base import (
BaseCommand as BaseCommand,
CommandError as CommandError,
no_translations as no_translations,
)
from django.core.management.sql import (
emit_post_migrate_signal as emit_post_migrate_signal,
emit_pre_migrate_signal as emit_pre_migrate_signal,
)
from django.db import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS, connections as connections, router as router
from django.db.migrations.autodetector import MigrationAutodetector as MigrationAutodetector
from django.db.migrations.executor import MigrationExecutor as MigrationExecutor
from django.db.migrations.loader import AmbiguityError as AmbiguityError
from django.db.migrations.operations.base import Operation
from django.db.migrations.state import ModelState as ModelState, ProjectState as ProjectState
from django.utils.module_loading import module_has_submodule as module_has_submodule
from django.utils.text import Truncator as Truncator
from typing import Any, List, Optional
class Command(BaseCommand):
verbosity: int = ...
interactive: bool = ...
start: float = ...
def migration_progress_callback(self, action: str, migration: Optional[Any] = ..., fake: bool = ...) -> None: ...
def sync_apps(self, connection: Any, app_labels: List[str]) -> None: ...
@staticmethod
def describe_operation(operation: Operation, backwards: bool) -> str: ...

View File

@@ -0,0 +1,6 @@
from django.core.mail import mail_admins as mail_admins, mail_managers as mail_managers, send_mail as send_mail
from django.core.management.base import BaseCommand as BaseCommand
from django.utils import timezone as timezone
class Command(BaseCommand):
missing_args_message: str = ...

View File

@@ -0,0 +1,9 @@
from django.core.management import BaseCommand as BaseCommand, CommandError as CommandError
from django.utils.datastructures import OrderedSet as OrderedSet
from typing import Any, List
class Command(BaseCommand):
shells: List[str] = ...
def ipython(self, options: Any) -> None: ...
def bpython(self, options: Any) -> None: ...
def python(self, options: Any) -> None: ...

View File

@@ -0,0 +1,10 @@
from django.apps import apps as apps
from django.core.management.base import BaseCommand as BaseCommand
from django.db import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS, connections as connections
from django.db.migrations.loader import MigrationLoader as MigrationLoader
from typing import Any, List, Optional
class Command(BaseCommand):
verbosity: int = ...
def show_list(self, connection: Any, app_names: Optional[List[str]] = ...) -> None: ...
def show_plan(self, connection: Any, app_names: Optional[List[str]] = ...) -> None: ...

View File

@@ -0,0 +1,6 @@
from django.core.management.base import BaseCommand as BaseCommand
from django.core.management.sql import sql_flush as sql_flush
from django.db import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS, connections as connections
class Command(BaseCommand):
output_transaction: bool = ...

View File

@@ -0,0 +1,9 @@
from django.apps import apps as apps
from django.core.management.base import BaseCommand as BaseCommand, CommandError as CommandError
from django.db import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS, connections as connections
from django.db.migrations.loader import AmbiguityError as AmbiguityError, MigrationLoader as MigrationLoader
from typing import Any
class Command(BaseCommand):
output_transaction: bool = ...
def execute(self, *args: Any, **options: Any): ...

View File

@@ -0,0 +1,4 @@
from django.core.management.base import AppCommand as AppCommand
from django.db import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS, connections as connections
class Command(AppCommand): ...

View File

@@ -0,0 +1,15 @@
from django.apps import apps as apps
from django.conf import settings as settings
from django.core.management.base import BaseCommand as BaseCommand, CommandError as CommandError
from django.db import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS, connections as connections, migrations as migrations
from django.db.migrations.migration import Migration
from django.db.migrations.loader import AmbiguityError as AmbiguityError, MigrationLoader as MigrationLoader
from django.db.migrations.migration import SwappableTuple as SwappableTuple
from django.db.migrations.optimizer import MigrationOptimizer as MigrationOptimizer
from django.db.migrations.writer import MigrationWriter as MigrationWriter
from django.utils.version import get_docs_version as get_docs_version
class Command(BaseCommand):
verbosity: int = ...
interactive: bool = ...
def find_migration(self, loader: MigrationLoader, app_label: str, name: str) -> Migration: ...

View File

@@ -0,0 +1,5 @@
from django.core.management.templates import TemplateCommand as TemplateCommand
from typing import Any
class Command(TemplateCommand):
missing_args_message: str = ...

View File

@@ -0,0 +1,5 @@
from ..utils import get_random_secret_key as get_random_secret_key
from django.core.management.templates import TemplateCommand as TemplateCommand
class Command(TemplateCommand):
missing_args_message: str = ...

View File

@@ -0,0 +1,10 @@
from django.conf import settings as settings
from django.core.management.base import BaseCommand as BaseCommand
from django.core.management.utils import get_command_line_option as get_command_line_option
from django.test.utils import get_runner as get_runner
from typing import Any
class Command(BaseCommand):
test_runner: Any = ...
def run_from_argv(self, argv: Any) -> None: ...
def add_arguments(self, parser: Any) -> None: ...

View File

@@ -1,4 +1,4 @@
from typing import List, Optional, Set, Tuple, Type from typing import Any, List, Optional, Set, Tuple, Type
from django.apps.config import AppConfig from django.apps.config import AppConfig
from django.db.models.base import Model from django.db.models.base import Model
@@ -8,3 +8,4 @@ def handle_extensions(extensions: List[str]) -> Set[str]: ...
def find_command(cmd: str, path: Optional[str] = ..., pathext: Optional[str] = ...) -> Optional[str]: ... def find_command(cmd: str, path: Optional[str] = ..., pathext: Optional[str] = ...) -> Optional[str]: ...
def get_random_secret_key(): ... def get_random_secret_key(): ...
def parse_apps_and_model_labels(labels: List[str]) -> Tuple[Set[Type[Model]], Set[AppConfig]]: ... def parse_apps_and_model_labels(labels: List[str]) -> Tuple[Set[Type[Model]], Set[AppConfig]]: ...
def get_command_line_option(argv: Any, option: Any): ...

View File

@@ -6,7 +6,7 @@ class SignatureExpired(BadSignature): ...
def b64_encode(s: bytes) -> bytes: ... def b64_encode(s: bytes) -> bytes: ...
def b64_decode(s: bytes) -> bytes: ... def b64_decode(s: bytes) -> bytes: ...
def base64_hmac(salt: str, value: Union[bytes, str], key: Union[bytes, str]) -> str: ... def base64_hmac(salt: str, value: Union[bytes, str], key: Union[bytes, str], algorithm: str = ...) -> str: ...
def get_cookie_signer(salt: str = ...) -> TimestampSigner: ... def get_cookie_signer(salt: str = ...) -> TimestampSigner: ...
class Serializer(Protocol): class Serializer(Protocol):
@@ -32,7 +32,14 @@ class Signer:
key: str = ... key: str = ...
sep: str = ... sep: str = ...
salt: str = ... salt: str = ...
def __init__(self, key: Optional[Union[bytes, str]] = ..., sep: str = ..., salt: Optional[str] = ...) -> None: ... algorithm: str = ...
def __init__(
self,
key: Optional[Union[bytes, str]] = ...,
sep: str = ...,
salt: Optional[str] = ...,
algorithm: Optional[str] = ...,
) -> None: ...
def signature(self, value: Union[bytes, str]) -> str: ... def signature(self, value: Union[bytes, str]) -> str: ...
def sign(self, value: str) -> str: ... def sign(self, value: str) -> str: ...
def unsign(self, signed_value: str) -> str: ... def unsign(self, signed_value: str) -> str: ...

View File

@@ -3,6 +3,13 @@ from typing import Any, Dict, Optional, Sequence, Set, Tuple, Union
from django.db.migrations.migration import Migration from django.db.migrations.migration import Migration
from django.db.migrations.state import ProjectState from django.db.migrations.state import ProjectState
from .exceptions import (
AmbiguityError as AmbiguityError,
BadMigrationError as BadMigrationError,
InconsistentMigrationHistory as InconsistentMigrationHistory,
NodeNotFoundError as NodeNotFoundError,
)
MIGRATIONS_MODULE_NAME: str MIGRATIONS_MODULE_NAME: str
class MigrationLoader: class MigrationLoader:

View File

@@ -10,8 +10,8 @@ class Operation:
def state_forwards(self, app_label: Any, state: Any) -> None: ... def state_forwards(self, app_label: Any, state: Any) -> None: ...
def database_forwards(self, app_label: Any, schema_editor: Any, from_state: Any, to_state: Any) -> None: ... def database_forwards(self, app_label: Any, schema_editor: Any, from_state: Any, to_state: Any) -> None: ...
def database_backwards(self, app_label: Any, schema_editor: Any, from_state: Any, to_state: Any) -> None: ... def database_backwards(self, app_label: Any, schema_editor: Any, from_state: Any, to_state: Any) -> None: ...
def describe(self): ... def describe(self) -> str: ...
def references_model(self, name: str, app_label: str = ...) -> bool: ... def references_model(self, name: str, app_label: str = ...) -> bool: ...
def references_field(self, model_name: str, name: str, app_label: str = ...) -> bool: ... def references_field(self, model_name: str, name: str, app_label: str = ...) -> bool: ...
def allow_migrate_model(self, connection_alias: Any, model: Any): ... def allow_migrate_model(self, connection_alias: Any, model: Any) -> bool: ...
def reduce(self, operation: Operation, in_between: List[Operation], app_label: str = ...) -> bool: ... def reduce(self, operation: Operation, in_between: List[Operation], app_label: str = ...) -> bool: ...

View File

@@ -17,6 +17,7 @@ from .fields import (
IntegerField as IntegerField, IntegerField as IntegerField,
PositiveIntegerField as PositiveIntegerField, PositiveIntegerField as PositiveIntegerField,
PositiveSmallIntegerField as PositiveSmallIntegerField, PositiveSmallIntegerField as PositiveSmallIntegerField,
PositiveBigIntegerField as PositiveBigIntegerField,
SmallIntegerField as SmallIntegerField, SmallIntegerField as SmallIntegerField,
BigIntegerField as BigIntegerField, BigIntegerField as BigIntegerField,
FloatField as FloatField, FloatField as FloatField,
@@ -60,6 +61,7 @@ from .fields.files import (
FileDescriptor as FileDescriptor, FileDescriptor as FileDescriptor,
) )
from .fields.proxy import OrderWrt as OrderWrt from .fields.proxy import OrderWrt as OrderWrt
from .fields.json import JSONField as JSONField
from .deletion import ( from .deletion import (
CASCADE as CASCADE, CASCADE as CASCADE,
@@ -130,6 +132,7 @@ from . import signals as signals
from .constraints import ( from .constraints import (
BaseConstraint as BaseConstraint, BaseConstraint as BaseConstraint,
CheckConstraint as CheckConstraint, CheckConstraint as CheckConstraint,
Deferrable as Deferrable,
UniqueConstraint as UniqueConstraint, UniqueConstraint as UniqueConstraint,
) )

View File

@@ -1,4 +1,4 @@
from typing import Any, Callable, Collection, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar, Union from typing import Any, Callable, Collection, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar, Union, ClassVar
from django.core.checks.messages import CheckMessage from django.core.checks.messages import CheckMessage
from django.core.exceptions import ( from django.core.exceptions import (
@@ -26,7 +26,7 @@ class Model(metaclass=ModelBase):
class Meta: ... class Meta: ...
_meta: Options[Any] _meta: Options[Any]
_default_manager: BaseManager[Model] _default_manager: BaseManager[Model]
objects: BaseManager[Any] objects: ClassVar[BaseManager[Any]]
pk: Any = ... pk: Any = ...
_state: ModelState _state: ModelState
def __init__(self: _Self, *args, **kwargs) -> None: ... def __init__(self: _Self, *args, **kwargs) -> None: ...

View File

@@ -1,3 +1,4 @@
import enum
from typing import Any, Optional, Sequence, Tuple, Type, TypeVar from typing import Any, Optional, Sequence, Tuple, Type, TypeVar
from django.db.backends.base.schema import BaseDatabaseSchemaEditor from django.db.backends.base.schema import BaseDatabaseSchemaEditor
@@ -6,6 +7,10 @@ from django.db.models.query_utils import Q
_T = TypeVar("_T", bound="BaseConstraint") _T = TypeVar("_T", bound="BaseConstraint")
class Deferrable(enum.Enum):
DEFERRED: str
IMMEDIATE: str
class BaseConstraint: class BaseConstraint:
name: str name: str
def __init__(self, name: str) -> None: ... def __init__(self, name: str) -> None: ...
@@ -24,4 +29,7 @@ class CheckConstraint(BaseConstraint):
class UniqueConstraint(BaseConstraint): class UniqueConstraint(BaseConstraint):
fields: Tuple[str] fields: Tuple[str]
condition: Optional[Q] condition: Optional[Q]
def __init__(self, *, fields: Sequence[str], name: str, condition: Optional[Q] = ...): ... deferrable: Optional[Deferrable]
def __init__(
self, *, fields: Sequence[str], name: str, condition: Optional[Q] = ..., deferrable: Optional[Deferrable] = ...
) -> None: ...

View File

@@ -142,6 +142,7 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
def cached_col(self) -> Col: ... def cached_col(self) -> Col: ...
def value_from_object(self, obj: Model) -> _GT: ... def value_from_object(self, obj: Model) -> _GT: ...
def get_attname(self) -> str: ... def get_attname(self) -> str: ...
def value_to_string(self, obj: Model) -> str: ...
class IntegerField(Field[_ST, _GT]): class IntegerField(Field[_ST, _GT]):
_pyi_private_set_type: Union[float, int, str, Combinable] _pyi_private_set_type: Union[float, int, str, Combinable]
@@ -153,6 +154,7 @@ class PositiveIntegerRelDbTypeMixin:
class PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField[_ST, _GT]): ... class PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField[_ST, _GT]): ...
class PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField[_ST, _GT]): ... class PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField[_ST, _GT]): ...
class PositiveBigIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField[_ST, _GT]): ...
class SmallIntegerField(IntegerField[_ST, _GT]): ... class SmallIntegerField(IntegerField[_ST, _GT]): ...
class BigIntegerField(IntegerField[_ST, _GT]): ... class BigIntegerField(IntegerField[_ST, _GT]): ...

View File

@@ -38,10 +38,10 @@ class FileField(Field):
upload_to: Union[str, Callable] = ... upload_to: Union[str, Callable] = ...
def __init__( def __init__(
self, self,
upload_to: Union[str, Callable, Path] = ...,
storage: Optional[Union[Storage, Callable[[], Storage]]] = ...,
verbose_name: Optional[Union[str, bytes]] = ..., verbose_name: Optional[Union[str, bytes]] = ...,
name: Optional[str] = ..., name: Optional[str] = ...,
upload_to: Union[str, Callable, Path] = ...,
storage: Optional[Union[Storage, Callable[[], Storage]]] = ...,
max_length: Optional[int] = ..., max_length: Optional[int] = ...,
unique: bool = ..., unique: bool = ...,
blank: bool = ..., blank: bool = ...,

View File

@@ -2,103 +2,42 @@ from . import Field
from .mixins import CheckFieldDefaultMixin from .mixins import CheckFieldDefaultMixin
from django.db.models import lookups from django.db.models import lookups
from django.db.models.lookups import PostgresOperatorLookup, Transform from django.db.models.lookups import PostgresOperatorLookup, Transform
from typing import Any, Optional from typing import Any, Optional, Callable
class JSONField(CheckFieldDefaultMixin, Field): class JSONField(CheckFieldDefaultMixin, Field):
empty_strings_allowed: bool = ...
description: Any = ...
default_error_messages: Any = ...
encoder: Any = ...
decoder: Any = ...
def __init__( def __init__(
self, self,
verbose_name: Optional[Any] = ..., verbose_name: Optional[str] = ...,
name: Optional[Any] = ..., name: Optional[str] = ...,
encoder: Optional[Any] = ..., encoder: Optional[Callable] = ...,
decoder: Optional[Any] = ..., decoder: Optional[Callable] = ...,
**kwargs: Any **kwargs: Any
) -> None: ... ) -> None: ...
def check(self, **kwargs: Any): ...
def deconstruct(self): ...
def from_db_value(self, value: Any, expression: Any, connection: Any): ...
def get_internal_type(self): ...
def get_prep_value(self, value: Any): ...
def get_transform(self, name: Any): ...
def validate(self, value: Any, model_instance: Any) -> None: ...
def value_to_string(self, obj: Any): ...
def formfield(self, **kwargs: Any): ...
class DataContains(PostgresOperatorLookup): class DataContains(PostgresOperatorLookup): ...
lookup_name: str = ... class ContainedBy(PostgresOperatorLookup): ...
postgres_operator: str = ...
def as_sql(self, compiler: Any, connection: Any): ...
class ContainedBy(PostgresOperatorLookup): class HasKeyLookup(PostgresOperatorLookup): ...
lookup_name: str = ... class HasKey(HasKeyLookup): ...
postgres_operator: str = ... class HasKeys(HasKeyLookup): ...
def as_sql(self, compiler: Any, connection: Any): ... class HasAnyKeys(HasKeys): ...
class JSONExact(lookups.Exact): ...
class HasKeyLookup(PostgresOperatorLookup):
logical_operator: Any = ...
def as_sql(self, compiler: Any, connection: Any, template: Optional[Any] = ...): ...
def as_mysql(self, compiler: Any, connection: Any): ...
def as_oracle(self, compiler: Any, connection: Any): ...
lhs: Any = ...
rhs: Any = ...
def as_postgresql(self, compiler: Any, connection: Any): ...
def as_sqlite(self, compiler: Any, connection: Any): ...
class HasKey(HasKeyLookup):
lookup_name: str = ...
postgres_operator: str = ...
prepare_rhs: bool = ...
class HasKeys(HasKeyLookup):
lookup_name: str = ...
postgres_operator: str = ...
logical_operator: str = ...
def get_prep_lookup(self): ...
class HasAnyKeys(HasKeys):
lookup_name: str = ...
postgres_operator: str = ...
logical_operator: str = ...
class JSONExact(lookups.Exact):
can_use_none_as_rhs: bool = ...
def process_rhs(self, compiler: Any, connection: Any): ...
class KeyTransform(Transform): class KeyTransform(Transform):
postgres_operator: str = ...
postgres_nested_operator: str = ...
key_name: Any = ... key_name: Any = ...
def __init__(self, key_name: Any, *args: Any, **kwargs: Any) -> None: ... def __init__(self, key_name: Any, *args: Any, **kwargs: Any) -> None: ...
def preprocess_lhs(self, compiler: Any, connection: Any, lhs_only: bool = ...): ... def preprocess_lhs(self, compiler: Any, connection: Any, lhs_only: bool = ...): ...
def as_mysql(self, compiler: Any, connection: Any): ...
def as_oracle(self, compiler: Any, connection: Any): ...
def as_postgresql(self, compiler: Any, connection: Any): ...
class KeyTextTransform(KeyTransform):
postgres_operator: str = ...
postgres_nested_operator: str = ...
class KeyTextTransform(KeyTransform): ...
class KeyTransformTextLookupMixin: class KeyTransformTextLookupMixin:
def __init__(self, key_transform: Any, *args: Any, **kwargs: Any) -> None: ... def __init__(self, key_transform: Any, *args: Any, **kwargs: Any) -> None: ...
class CaseInsensitiveMixin: class CaseInsensitiveMixin: ...
def process_rhs(self, compiler: Any, connection: Any): ... class KeyTransformIsNull(lookups.IsNull): ...
class KeyTransformIn(lookups.In): ...
class KeyTransformIsNull(lookups.IsNull):
def as_oracle(self, compiler: Any, connection: Any): ...
def as_sqlite(self, compiler: Any, connection: Any): ...
class KeyTransformIn(lookups.In):
def process_rhs(self, compiler: Any, connection: Any): ...
class KeyTransformExact(JSONExact):
def process_rhs(self, compiler: Any, connection: Any): ...
def as_oracle(self, compiler: Any, connection: Any): ...
class KeyTransformExact(JSONExact): ...
class KeyTransformIExact(CaseInsensitiveMixin, KeyTransformTextLookupMixin, lookups.IExact): ... class KeyTransformIExact(CaseInsensitiveMixin, KeyTransformTextLookupMixin, lookups.IExact): ...
class KeyTransformIContains(CaseInsensitiveMixin, KeyTransformTextLookupMixin, lookups.IContains): ... class KeyTransformIContains(CaseInsensitiveMixin, KeyTransformTextLookupMixin, lookups.IContains): ...
class KeyTransformStartsWith(KeyTransformTextLookupMixin, lookups.StartsWith): ... class KeyTransformStartsWith(KeyTransformTextLookupMixin, lookups.StartsWith): ...
@@ -108,9 +47,7 @@ class KeyTransformIEndsWith(CaseInsensitiveMixin, KeyTransformTextLookupMixin, l
class KeyTransformRegex(KeyTransformTextLookupMixin, lookups.Regex): ... class KeyTransformRegex(KeyTransformTextLookupMixin, lookups.Regex): ...
class KeyTransformIRegex(CaseInsensitiveMixin, KeyTransformTextLookupMixin, lookups.IRegex): ... class KeyTransformIRegex(CaseInsensitiveMixin, KeyTransformTextLookupMixin, lookups.IRegex): ...
class KeyTransformNumericLookupMixin: class KeyTransformNumericLookupMixin: ...
def process_rhs(self, compiler: Any, connection: Any): ...
class KeyTransformLt(KeyTransformNumericLookupMixin, lookups.LessThan): ... class KeyTransformLt(KeyTransformNumericLookupMixin, lookups.LessThan): ...
class KeyTransformLte(KeyTransformNumericLookupMixin, lookups.LessThanOrEqual): ... class KeyTransformLte(KeyTransformNumericLookupMixin, lookups.LessThanOrEqual): ...
class KeyTransformGt(KeyTransformNumericLookupMixin, lookups.GreaterThan): ... class KeyTransformGt(KeyTransformNumericLookupMixin, lookups.GreaterThan): ...

View File

@@ -60,7 +60,7 @@ class FieldGetDbPrepValueIterableMixin(FieldGetDbPrepValueMixin):
) -> Any: ... ) -> Any: ...
class PostgresOperatorLookup(FieldGetDbPrepValueMixin, Lookup): class PostgresOperatorLookup(FieldGetDbPrepValueMixin, Lookup):
postgres_operator: Any = ... postgres_operator: str = ...
def as_postgresql(self, compiler: Any, connection: Any): ... def as_postgresql(self, compiler: Any, connection: Any): ...
class Exact(FieldGetDbPrepValueMixin, BuiltinLookup): ... class Exact(FieldGetDbPrepValueMixin, BuiltinLookup): ...

View File

@@ -1,5 +1,6 @@
from typing import Tuple, Type, Union from typing import Any, Generator, MutableMapping, Tuple, Type, Union
from django.db.models.base import Model from django.db.models.base import Model
def make_model_tuple(model: Union[Type[Model], str]) -> Tuple[str, str]: ... def make_model_tuple(model: Union[Type[Model], str]) -> Tuple[str, str]: ...
def resolve_callables(mapping: MutableMapping[str, Any]) -> Generator[Tuple[str, Any], None, None]: ...

View File

@@ -255,7 +255,7 @@ class ModelChoiceField(ChoiceField):
to_field_name: None = ... to_field_name: None = ...
def __init__( def __init__(
self, self,
queryset: Optional[Union[Manager, QuerySet]], queryset: Optional[Union[Manager, _BaseQuerySet]],
*, *,
empty_label: Optional[str] = ..., empty_label: Optional[str] = ...,
required: bool = ..., required: bool = ...,
@@ -282,7 +282,7 @@ class ModelMultipleChoiceField(ModelChoiceField):
widget: Any = ... widget: Any = ...
hidden_widget: Any = ... hidden_widget: Any = ...
default_error_messages: Any = ... default_error_messages: Any = ...
def __init__(self, queryset: _BaseQuerySet, **kwargs: Any) -> None: ... def __init__(self, queryset: Optional[Union[Manager, _BaseQuerySet]], **kwargs: Any) -> None: ...
def _get_foreign_key( def _get_foreign_key(
parent_model: Type[Model], model: Type[Model], fk_name: Optional[str] = ..., can_fail: bool = ... parent_model: Type[Model], model: Type[Model], fk_name: Optional[str] = ..., can_fail: bool = ...

View File

@@ -71,6 +71,8 @@ class HiddenInput(Input):
class MultipleHiddenInput(HiddenInput): ... class MultipleHiddenInput(HiddenInput): ...
class FileInput(Input): ... class FileInput(Input): ...
FILE_INPUT_CONTRADICTION: Any
class ClearableFileInput(FileInput): class ClearableFileInput(FileInput):
clear_checkbox_label: Any = ... clear_checkbox_label: Any = ...
initial_text: Any = ... initial_text: Any = ...

View File

@@ -6,24 +6,35 @@ from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Type, U
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
from django.http.cookie import SimpleCookie from django.http.cookie import SimpleCookie
from django.test.client import Client from django.test.client import Client
from django.utils.datastructures import CaseInsensitiveMapping
from django.template import Context, Template from django.template import Context, Template
from django.urls import ResolverMatch from django.urls import ResolverMatch
class BadHeaderError(ValueError): ... class BadHeaderError(ValueError): ...
class ResponseHeaders(CaseInsensitiveMapping):
store: Dict[str, str] = ...
def __init__(self, data: Dict[str, str]) -> None: ...
def _convert_to_charset(self, value: Union[bytes, str], charset: str, mime_encode: str=...) -> str: ...
def __delitem__(self, key: str) -> None: ...
def __setitem__(self, key: str, value: str) -> None: ...
def pop(self, key: str, default: Optional[str] = ...) -> str: ...
def setdefault(self, key: str, value: str) -> None: ...
class HttpResponseBase(Iterable[Any]): class HttpResponseBase(Iterable[Any]):
status_code: int = ... status_code: int = ...
cookies: SimpleCookie = ... cookies: SimpleCookie = ...
reason_phrase: str = ... reason_phrase: str = ...
charset: str = ... charset: str = ...
closed: bool = ... closed: bool = ...
headers: ResponseHeaders = ...
def __init__( def __init__(
self, self,
content_type: Optional[str] = ..., content_type: Optional[str] = ...,
status: Optional[int] = ..., status: Optional[int] = ...,
reason: Optional[str] = ..., reason: Optional[str] = ...,
charset: Optional[str] = ..., charset: Optional[str] = ...,
headers: Optional[Dict[str, str]] = ...,
) -> None: ... ) -> None: ...
def serialize_headers(self) -> bytes: ... def serialize_headers(self) -> bytes: ...
def __setitem__(self, header: Union[str, bytes], value: Union[str, bytes, int]) -> None: ... def __setitem__(self, header: Union[str, bytes], value: Union[str, bytes, int]) -> None: ...

View File

@@ -1,4 +1,4 @@
from typing import Any, Callable, Dict, List, Optional, Tuple, Union from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar, Union, overload
from django.template.base import FilterExpression, Parser, Origin, Token from django.template.base import FilterExpression, Parser, Origin, Token
from django.template.context import Context from django.template.context import Context
@@ -8,31 +8,38 @@ from .base import Node, Template
class InvalidTemplateLibrary(Exception): ... class InvalidTemplateLibrary(Exception): ...
_C = TypeVar("_C", bound=Callable[..., Any])
class Library: class Library:
filters: Dict[str, Callable] = ... filters: Dict[str, Callable] = ...
tags: Dict[str, Callable] = ... tags: Dict[str, Callable] = ...
def __init__(self) -> None: ... def __init__(self) -> None: ...
def tag( @overload
self, name: Optional[Union[Callable, str]] = ..., compile_function: Optional[Union[Callable, str]] = ... def tag(self, name: _C) -> _C: ...
) -> Callable: ... @overload
def tag_function(self, func: Callable) -> Callable: ... def tag(self, name: str, compile_function: _C) -> _C: ...
def filter( @overload
self, def tag(self, name: Optional[str] = ..., compile_function: None = ...) -> Callable[[_C], _C]: ...
name: Optional[Union[Callable, str]] = ..., def tag_function(self, func: _C) -> _C: ...
filter_func: Optional[Union[Callable, str]] = ..., @overload
**flags: Any def filter(self, name: _C, filter_func: None = ..., **flags: Any) -> _C: ...
) -> Callable: ... @overload
def filter_function(self, func: Callable, **flags: Any) -> Callable: ... def filter(self, name: Optional[str], filter_func: _C, **flags: Any) -> _C: ...
@overload
def filter(self, name: Optional[str] = ..., filter_func: None = ..., **flags: Any) -> Callable[[_C], _C]: ...
@overload
def simple_tag(self, func: _C) -> _C: ...
@overload
def simple_tag( def simple_tag(
self, func: Optional[Union[Callable, str]] = ..., takes_context: Optional[bool] = ..., name: Optional[str] = ... self, takes_context: Optional[bool] = ..., name: Optional[str] = ...
) -> Callable: ... ) -> Callable[[_C], _C]: ...
def inclusion_tag( def inclusion_tag(
self, self,
filename: Union[Template, str], filename: Union[Template, str],
func: None = ..., func: None = ...,
takes_context: Optional[bool] = ..., takes_context: Optional[bool] = ...,
name: Optional[str] = ..., name: Optional[str] = ...,
) -> Callable: ... ) -> Callable[[_C], _C]: ...
class TagHelperNode(Node): class TagHelperNode(Node):
func: Any = ... func: Any = ...

View File

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

View File

@@ -15,7 +15,7 @@ def get_conditional_response(
last_modified: Optional[int] = ..., last_modified: Optional[int] = ...,
response: Optional[HttpResponse] = ..., response: Optional[HttpResponse] = ...,
) -> Optional[HttpResponse]: ... ) -> Optional[HttpResponse]: ...
def patch_response_headers(response: HttpResponseBase, cache_timeout: float = ...) -> None: ... def patch_response_headers(response: HttpResponseBase, cache_timeout: Optional[int] = ...) -> None: ...
def add_never_cache_headers(response: HttpResponseBase) -> None: ... def add_never_cache_headers(response: HttpResponseBase) -> None: ...
def patch_vary_headers(response: HttpResponseBase, newheaders: Tuple[str]) -> None: ... def patch_vary_headers(response: HttpResponseBase, newheaders: Tuple[str]) -> None: ...
def has_vary_header(response: HttpResponse, header_query: str) -> bool: ... def has_vary_header(response: HttpResponse, header_query: str) -> bool: ...

View File

@@ -3,7 +3,9 @@ from typing import Callable, Optional, Union
using_sysrandom: bool using_sysrandom: bool
def salted_hmac(key_salt: str, value: Union[bytes, str], secret: Optional[Union[bytes, str]] = ...) -> HMAC: ... def salted_hmac(
key_salt: str, value: Union[bytes, str], secret: Optional[Union[bytes, str]] = ..., algorithm: str = ...
) -> HMAC: ...
def get_random_string(length: int = ..., allowed_chars: str = ...) -> str: ... def get_random_string(length: int = ..., allowed_chars: str = ...) -> str: ...
def constant_time_compare(val1: Union[bytes, str], val2: Union[bytes, str]) -> bool: ... def constant_time_compare(val1: Union[bytes, str], val2: Union[bytes, str]) -> bool: ...
def pbkdf2( def pbkdf2(

View File

@@ -3,6 +3,8 @@ from typing import Any, Callable, Iterable, Optional, Type, Union, TypeVar
from django.utils.deprecation import MiddlewareMixin from django.utils.deprecation import MiddlewareMixin
from django.views.generic.base import View from django.views.generic.base import View
from django.utils.functional import classproperty as classproperty
_T = TypeVar("_T", bound=Union[View, Callable]) # Any callable _T = TypeVar("_T", bound=Union[View, Callable]) # Any callable
class classonlymethod(classmethod): ... class classonlymethod(classmethod): ...
@@ -12,9 +14,3 @@ def decorator_from_middleware_with_args(middleware_class: type) -> Callable: ...
def decorator_from_middleware(middleware_class: type) -> Callable: ... def decorator_from_middleware(middleware_class: type) -> Callable: ...
def available_attrs(fn: Callable): ... def available_attrs(fn: Callable): ...
def make_middleware_decorator(middleware_class: Type[MiddlewareMixin]) -> Callable: ... def make_middleware_decorator(middleware_class: Type[MiddlewareMixin]) -> Callable: ...
class classproperty:
fget: Optional[Callable] = ...
def __init__(self, method: Optional[Callable] = ...) -> None: ...
def __get__(self, instance: Any, cls: Optional[type] = ...) -> Any: ...
def getter(self, method: Callable) -> classproperty: ...

View File

@@ -61,3 +61,9 @@ _PartitionMember = TypeVar("_PartitionMember")
def partition( def partition(
predicate: Callable, values: List[_PartitionMember] predicate: Callable, values: List[_PartitionMember]
) -> Tuple[List[_PartitionMember], List[_PartitionMember]]: ... ) -> Tuple[List[_PartitionMember], List[_PartitionMember]]: ...
class classproperty:
fget: Optional[Callable] = ...
def __init__(self, method: Optional[Callable] = ...) -> None: ...
def __get__(self, instance: Any, cls: Optional[type] = ...) -> Any: ...
def getter(self, method: Callable) -> classproperty: ...

View File

@@ -1,6 +1,6 @@
import functools import functools
from contextlib import ContextDecorator from contextlib import ContextDecorator
from typing import Any, Optional, Callable from typing import Any, Optional, Callable, Union
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
@@ -42,10 +42,12 @@ def npgettext(context: str, singular: str, plural: str, number: int) -> str: ...
gettext_lazy = gettext gettext_lazy = gettext
ugettext_lazy = ugettext ugettext_lazy = ugettext
pgettext_lazy = pgettext pgettext_lazy = pgettext
ngettext_lazy = ngettext
ungettext_lazy = ungettext
npgettext_lazy = npgettext
def ngettext_lazy(singular: str, plural: str, number: Union[int, str, None]) -> str: ...
ungettext_lazy = ngettext_lazy
def npgettext_lazy(context: str, singular: str, plural: str, number: Union[int, str, None]) -> str: ...
def activate(language: str) -> None: ... def activate(language: str) -> None: ...
def deactivate() -> None: ... def deactivate() -> None: ...

View File

@@ -1,4 +1,4 @@
from typing import Any, Callable, Dict, Optional, Sequence, Type, Union from typing import Any, Callable, Dict, Generic, Optional, Sequence, Type, TypeVar, Union
from django.forms.forms import BaseForm from django.forms.forms import BaseForm
from django.forms.models import BaseModelForm from django.forms.models import BaseModelForm
@@ -8,6 +8,8 @@ from typing_extensions import Literal
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
_FormT = TypeVar("_FormT", bound=BaseForm)
class AbstractFormMixin(ContextMixin): class AbstractFormMixin(ContextMixin):
initial: Dict[str, Any] = ... initial: Dict[str, Any] = ...
form_class: Optional[Type[BaseForm]] = ... form_class: Optional[Type[BaseForm]] = ...
@@ -18,11 +20,11 @@ class AbstractFormMixin(ContextMixin):
def get_form_kwargs(self) -> Dict[str, Any]: ... def get_form_kwargs(self) -> Dict[str, Any]: ...
def get_success_url(self) -> str: ... def get_success_url(self) -> str: ...
class FormMixin(AbstractFormMixin): class FormMixin(Generic[_FormT], AbstractFormMixin):
def get_form_class(self) -> Type[BaseForm]: ... def get_form_class(self) -> Type[_FormT]: ...
def get_form(self, form_class: Optional[Type[BaseForm]] = ...) -> BaseForm: ... def get_form(self, form_class: Optional[Type[_FormT]] = ...) -> BaseForm: ...
def form_valid(self, form: BaseForm) -> HttpResponse: ... def form_valid(self, form: _FormT) -> HttpResponse: ...
def form_invalid(self, form: BaseForm) -> HttpResponse: ... def form_invalid(self, form: _FormT) -> HttpResponse: ...
class ModelFormMixin(AbstractFormMixin, SingleObjectMixin): class ModelFormMixin(AbstractFormMixin, SingleObjectMixin):
fields: Optional[Union[Sequence[str], Literal["__all__"]]] = ... fields: Optional[Union[Sequence[str], Literal["__all__"]]] = ...
@@ -36,8 +38,8 @@ class ProcessFormView(View):
def post(self, request: HttpRequest, *args: str, **kwargs: Any) -> HttpResponse: ... def post(self, request: HttpRequest, *args: str, **kwargs: Any) -> HttpResponse: ...
def put(self, *args: str, **kwargs: Any) -> HttpResponse: ... def put(self, *args: str, **kwargs: Any) -> HttpResponse: ...
class BaseFormView(FormMixin, ProcessFormView): ... class BaseFormView(FormMixin[_FormT], ProcessFormView): ...
class FormView(TemplateResponseMixin, BaseFormView): ... class FormView(TemplateResponseMixin, BaseFormView[_FormT]): ...
class BaseCreateView(ModelFormMixin, ProcessFormView): ... class BaseCreateView(ModelFormMixin, ProcessFormView): ...
class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView): ... class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView): ...
class BaseUpdateView(ModelFormMixin, ProcessFormView): ... class BaseUpdateView(ModelFormMixin, ProcessFormView): ...

View File

@@ -3,7 +3,7 @@ from typing import Any, Optional
from django.http.request import HttpRequest from django.http.request import HttpRequest
from django.http.response import FileResponse from django.http.response import FileResponse
def serve(request: HttpRequest, path: str, document_root: str = ..., show_indexes: bool = ...) -> FileResponse: ... def serve(request: HttpRequest, path: str, document_root: Optional[str] = ..., show_indexes: bool = ...) -> FileResponse: ...
DEFAULT_DIRECTORY_INDEX_TEMPLATE: str DEFAULT_DIRECTORY_INDEX_TEMPLATE: str
template_translatable: Any template_translatable: Any

View File

@@ -0,0 +1,49 @@
# Extensions and monkey-patching for django-stubs
[![Build Status](https://travis-ci.com/typeddjango/django-stubs.svg?branch=master)](https://travis-ci.com/typeddjango/django-stubs)
[![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
[![Gitter](https://badges.gitter.im/mypy-django/Lobby.svg)](https://gitter.im/mypy-django/Lobby)
This package contains extensions and monkey-patching functions for the [django-stubs](https://github.com/typeddjango/django-stubs) package. Certain features of django-stubs (i.e. generic django classes that don't define the `__class_getitem__` method) require runtime monkey-patching, which can't be done with type stubs. These extensions were split into a separate package so library consumers don't need `mypy` as a runtime dependency ([#526](https://github.com/typeddjango/django-stubs/pull/526#pullrequestreview-525798031)).
## Installation
```bash
pip install django-stubs-ext
```
## Usage
In your Django application, use the following code:
```py
import django_stubs_ext
django_stubs_ext.monkeypatch()
```
This only needs to be called once, so the call to `monkeypatch` should be placed in your top-level settings.
## Version compatibility
Since django-stubs supports multiple Django versions, this package takes care to only monkey-patch the features needed by your django version, and decides which features to patch at runtime. This is completely safe, as (currently) we only add a `__class_getitem__` method that does nothing:
```py
@classmethod
def __class_getitem__(cls, *args, **kwargs):
return cls
```
## To get help
For help with django-stubs, please view the main repository at <https://github.com/typeddjango/django-stubs>
We have a Gitter chat here: <https://gitter.im/mypy-django/Lobby>
If you think you have a more generic typing issue, please refer to <https://github.com/python/mypy> and their Gitter.
## Contributing
The django-stubs-ext package is part of the [django-stubs](https://github.com/typeddjango/django-stubs) monorepo. If you would like to contribute, please view the django-stubs [contribution guide](https://github.com/typeddjango/django-stubs/blob/master/CONTRIBUTING.md).
You can always also reach out in gitter to discuss your contributions!

View File

@@ -0,0 +1,3 @@
from .patch import monkeypatch as monkeypatch
__all__ = ["monkeypatch"]

View File

@@ -0,0 +1,60 @@
from typing import Any, Generic, List, Optional, Tuple, Type, TypeVar
from django import VERSION as VERSION
from django.contrib.admin import ModelAdmin
from django.contrib.admin.options import BaseModelAdmin
from django.db.models.fields import Field
from django.db.models.manager import BaseManager
from django.db.models.query import QuerySet
from django.views.generic.edit import FormMixin
_T = TypeVar("_T")
_VersionSpec = Tuple[int, int]
class MPGeneric(Generic[_T]):
"""Create a data class to hold metadata about the gneric classes needing monkeypatching.
The `version` param is optional, and a value of `None` means that the monkeypatch is
version-independent.
This is slightly overkill for our purposes, but useful for future-proofing against any
possible issues we may run into with this method.
"""
def __init__(self, cls: Type[_T], version: Optional[_VersionSpec] = None):
"""Set the data fields, basic constructor."""
self.version = version
self.cls = cls
def __repr__(self) -> str:
"""Better representation in tests and debug."""
return "<MPGeneric: {}, versions={}>".format(self.cls, self.version or "all")
# certain django classes need to be generic, but lack the __class_getitem__ dunder needed to
# annotate them: https://github.com/typeddjango/django-stubs/issues/507
# this list stores them so `monkeypatch` can fix them when called
_need_generic: List[MPGeneric[Any]] = [
MPGeneric(ModelAdmin),
MPGeneric(FormMixin),
MPGeneric(BaseModelAdmin),
MPGeneric(Field),
# These types do have native `__class_getitem__` method since django 3.1:
MPGeneric(QuerySet, (3, 1)),
MPGeneric(BaseManager, (3, 1)),
]
# currently just adds the __class_getitem__ dunder. if more monkeypatching is needed, add it here
def monkeypatch() -> None:
"""Monkey patch django as necessary to work properly with mypy."""
suited_for_this_version = filter(
lambda spec: spec.version is None or VERSION[:2] <= spec.version,
_need_generic,
)
for el in suited_for_this_version:
el.cls.__class_getitem__ = classmethod(lambda cls, *args, **kwargs: cls)
__all__ = ["monkeypatch"]

View 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

View File

@@ -0,0 +1,2 @@
[metadata]
license_file = ../LICENSE.txt

42
django_stubs_ext/setup.py Normal file
View File

@@ -0,0 +1,42 @@
from distutils.core import setup
from setuptools import find_packages
with open("README.md") as f:
readme = f.read()
dependencies = [
"django",
]
setup(
name="django-stubs-ext",
version="0.1.0",
description="Monkey-patching and extensions for django-stubs",
long_description=readme,
long_description_content_type="text/markdown",
license="MIT",
url="https://github.com/typeddjango/django-stubs",
author="Simula Proxy",
author_email="3nki.nam.shub@gmail.com",
py_modules=[],
python_requires=">=3.6",
install_requires=dependencies,
packages=["django_stubs_ext", *find_packages(exclude=["scripts"])],
classifiers=[
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Typing :: Typed",
"Framework :: Django",
"Framework :: Django :: 2.2",
"Framework :: Django :: 3.0",
"Framework :: Django :: 3.1",
],
project_urls={
"Release notes": "https://github.com/typeddjango/django-stubs/releases",
},
)

View File

@@ -0,0 +1,67 @@
from contextlib import suppress
from typing import Optional
import pytest
from _pytest.fixtures import FixtureRequest
from _pytest.monkeypatch import MonkeyPatch
from typing_extensions import Protocol
import django_stubs_ext
from django_stubs_ext import patch
from django_stubs_ext.patch import _need_generic, _VersionSpec
class _MakeGenericClasses(Protocol):
"""Used to represent a type of ``make_generic_classes`` fixture."""
def __call__(self, django_version: Optional[_VersionSpec] = None) -> None:
...
@pytest.fixture(scope="function")
def make_generic_classes(
request: FixtureRequest,
monkeypatch: MonkeyPatch,
) -> _MakeGenericClasses:
def fin() -> None:
for el in _need_generic:
with suppress(AttributeError):
delattr(el.cls, "__class_getitem__")
def factory(django_version: Optional[_VersionSpec] = None) -> None:
if django_version is not None:
monkeypatch.setattr(patch, "VERSION", django_version)
django_stubs_ext.monkeypatch()
request.addfinalizer(fin)
return factory
def test_patched_generics(make_generic_classes: _MakeGenericClasses) -> None:
"""Test that the generics actually get patched."""
make_generic_classes()
for el in _need_generic:
if el.version is None:
assert el.cls[type] is el.cls # `type` is arbitrary
@pytest.mark.parametrize(
"django_version",
[
(2, 2),
(3, 0),
(3, 1),
(3, 2),
],
)
def test_patched_version_specific(
django_version: _VersionSpec,
make_generic_classes: _MakeGenericClasses,
) -> None:
"""Test version speicific types."""
make_generic_classes(django_version)
for el in _need_generic:
if el.version is not None and django_version <= el.version:
assert el.cls[int] is el.cls

View File

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

View File

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

View File

@@ -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,17 +138,18 @@ 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]:
contenttypes_in_apps = self.apps_registry.is_installed("django.contrib.contenttypes")
if contenttypes_in_apps:
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
expected_types = {} expected_types = {}
@@ -158,7 +157,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,20 +187,18 @@ 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
elif isinstance(field, GenericForeignKey): elif contenttypes_in_apps and isinstance(field, GenericForeignKey):
# 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 +227,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 +247,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 +267,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 +277,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 +286,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 +312,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 +362,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)

View File

@@ -1,39 +1,36 @@
ABSTRACT_USER_MODEL_FULLNAME = "django.contrib.auth.models.AbstractUser"
PERMISSION_MIXIN_CLASS_FULLNAME = "django.contrib.auth.models.PermissionsMixin"
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"

View File

@@ -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,21 @@ 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:], try:
method_type.arg_types[1:], original_arguments = method_node.arguments[1:]
method_node.arguments[1:]): except AttributeError:
original_arguments = []
for arg_name, arg_type, original_argument in zip(
method_type.arg_names[1:], method_type.arg_types[1:], original_arguments
):
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 +379,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(
variable=var,
type_annotation=bound_arg_type, type_annotation=bound_arg_type,
initializer=original_argument.initializer, initializer=original_argument.initializer,
kind=original_argument.kind) 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)

View File

@@ -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.models import process_model_class, set_auth_user_model_boolean_fields
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
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,29 @@ 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) with open(cast(str, config_file_path)) as handle:
parser.read_file(handle, 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 +106,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.BASEFORM_CLASS_FULLNAME: 1,
fullnames.FORM_CLASS_FULLNAME: 1, fullnames.FORM_CLASS_FULLNAME: 1,
fullnames.MODELFORM_CLASS_FULLNAME: 1})) fullnames.MODELFORM_CLASS_FULLNAME: 1,
},
)
else: else:
return {} return {}
@@ -144,17 +155,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 +196,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 +213,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 +264,23 @@ 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.PERMISSION_MIXIN_CLASS_FULLNAME) and attr_name == "is_superuser":
return partial(set_auth_user_model_boolean_fields, django_context=self.django_context)
if info and info.has_base(fullnames.HTTPREQUEST_CLASS_FULLNAME) and attr_name == "user":
return partial(request.set_auth_user_model_as_type_for_request_user, django_context=self.django_context) return partial(request.set_auth_user_model_as_type_for_request_user, django_context=self.django_context)
if info and info.has_base(fullnames.ABSTRACT_USER_MODEL_FULLNAME) and attr_name in ("is_staff", "is_active"):
return partial(set_auth_user_model_boolean_fields, 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

View File

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

View File

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

View File

@@ -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(
ctx,
expected_type=expected_types[actual_name], expected_type=expected_types[actual_name],
actual_type=actual_type, actual_type=actual_type,
error_message='Incompatible type for "{}" of "{}"'.format(actual_name, error_message=f'Incompatible type for "{actual_name}" of "{model_cls.__name__}"',
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")

View File

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

View File

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

View File

@@ -3,11 +3,9 @@ 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 AttributeContext, ClassDefContext
from mypy.plugins import common from mypy.plugins import common
from mypy.semanal import SemanticAnalyzer from mypy.semanal import SemanticAnalyzer
from mypy.types import AnyType, Instance from mypy.types import AnyType, Instance
@@ -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}",
args=[
Argument(
Var("kwargs", AnyType(TypeOfAny.explicit)),
AnyType(TypeOfAny.explicit), AnyType(TypeOfAny.explicit),
initializer=None, initializer=None,
kind=ARG_STAR2)], kind=ARG_STAR2,
return_type=return_type) )
common.add_method(self.ctx, ],
name='get_previous_by_{}'.format(field.attname), return_type=return_type,
args=[Argument(Var('kwargs', AnyType(TypeOfAny.explicit)), )
common.add_method(
self.ctx,
name=f"get_previous_by_{field.attname}",
args=[
Argument(
Var("kwargs", AnyType(TypeOfAny.explicit)),
AnyType(TypeOfAny.explicit), AnyType(TypeOfAny.explicit),
initializer=None, initializer=None,
kind=ARG_STAR2)], kind=ARG_STAR2,
return_type=return_type) )
],
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,
@@ -358,3 +355,9 @@ def process_model_class(ctx: ClassDefContext,
except helpers.IncompleteDefnException: except helpers.IncompleteDefnException:
if not ctx.api.final_iteration: if not ctx.api.final_iteration:
ctx.api.defer() ctx.api.defer()
def set_auth_user_model_boolean_fields(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
boolinfo = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), bool)
assert boolinfo is not None
return Instance(boolinfo, [])

Some files were not shown because too many files have changed in this diff Show More