29 Commits

Author SHA1 Message Date
Maxim Kurnikov
1af3a12f2c bump to 1.5.0 2020-03-15 00:59:58 +03:00
Marti Raudsepp
7af89ee6a6 Update to mypy 0.770 (#341) 2020-03-13 16:45:45 +03:00
Marti Raudsepp
afa16bfb74 Make decorator functions transparent to Mypy (#306)
By declaring return type as -> Callable[[_C], _C], Mypy can infer that
the decorated function has also the same arguments and return type as
the original.

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

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

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

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

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

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

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

Closes #206 
Refs #274

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

* Update README.md

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

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

* It should not fail right now 🤔

* Refactor validators a bit

* Fixes CI

* Update __init__.pyi
2019-12-21 15:44:26 +03:00
Maksim Kurnikov
cb123de105 BaseManager.from_queryset(): properly resolve methods for QuerySet defined in another file (#282)
* BaseManager.from_queryset() from another file

* only anal_type per argument

* add resolve for return_type

* fix mypy errors

* remove leftover comment
2019-12-18 20:01:20 +03:00
Maksim Kurnikov
38135f2d1f Merge pull request #281 from mkurnikov/update-deps
Update dev deps, mypy to 0.760
2019-12-18 00:13:11 +03:00
Maxim Kurnikov
998b659749 bump to 1.4.0 2019-12-18 00:03:29 +03:00
Maxim Kurnikov
72f69e1c5e remove unused ignores 2019-12-18 00:02:55 +03:00
Maxim Kurnikov
d666ecd36f update dev deps, mypy to 0.760 2019-12-17 23:50:50 +03:00
Maksim Kurnikov
c1af26c027 handle return value of anal_type properly (#280) 2019-12-17 23:36:44 +03:00
33 changed files with 575 additions and 188 deletions

111
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,111 @@
# How to contribute
## Tutorials
If you want to start working on this project,
you will need to get familiar with these projects:
- [Django docs](https://docs.djangoproject.com/en/dev/)
- [Typing in Python](https://inventwithpython.com/blog/2019/11/24/type-hints-for-busy-python-programmers/)
- [How to write custom mypy plugins](https://mypy.readthedocs.io/en/stable/extending_mypy.html)
- [Typechecking Django and DRF](https://sobolevn.me/2019/08/typechecking-django-and-drf) guide
- [Testing mypy stubs, plugins, and types](https://sobolevn.me/2019/08/testing-mypy-types) guide
It is also recommended to take a look at these resources:
- [Awesome Python Typing](https://github.com/typeddjango/awesome-python-typing)
## Dev documentation
TODO
## Dependencies
We use `pip` to manage the dependencies.
To install them you would need to activate your `virtualenv` and run `install` command:
```bash
pip install -r ./dev-requirements.txt
```
## Tests and linters
We use `mypy`, `pytest`, `flake8`, and `black` for quality control.
Here's [how we run our CI](https://github.com/typeddjango/django-stubs/blob/master/.travis.yml).
### Typechecking
To run typechecking use:
```bash
mypy ./mypy_django_plugin
```
### Testing
There are unit tests and type-related tests.
To run unit tests:
```bash
pytest
```
Type-related tests ensure that different Django versions do work correctly.
To run type-related tests:
```bash
python ./scripts/typecheck_tests.py --django_version=2.2
python ./scripts/typecheck_tests.py --django_version=3.0
```
Currently we only support two Django versions.
### Linting
To run auto-formatting:
```bash
isort -rc .
black django-stubs/
```
To run linting:
```bash
flake8
flake8 --config flake8-pyi.ini
```
## Submitting your code
We use [trunk based](https://trunkbaseddevelopment.com/)
development (we also sometimes call it `wemake-git-flow`).
What the point of this method?
1. We use protected `master` branch,
so the only way to push your code is via pull request
2. We use issue branches: to implement a new feature or to fix a bug
create a new branch named `issue-$TASKNUMBER`
3. Then create a pull request to `master` branch
4. We use `git tag`s to make releases, so we can track what has changed
since the latest release
So, this way we achieve an easy and scalable development process
which frees us from merging hell and long-living branches.
In this method, the latest version of the app is always in the `master` branch.
## Other help
You can contribute by spreading a word about this library.
It would also be a huge contribution to write
a short article on how you are using this project.
You can also share your best practices with us.

103
README.md
View File

@@ -1,14 +1,12 @@
<img src="http://mypy-lang.org/static/mypy_light.svg" alt="mypy logo" width="300px"/> <img src="http://mypy-lang.org/static/mypy_light.svg" alt="mypy logo" width="300px"/>
# pep484 stubs for Django framework # Typesafe Django Framework
[![Build Status](https://travis-ci.com/typeddjango/django-stubs.svg?branch=master)](https://travis-ci.com/typeddjango/django-stubs) [![Build Status](https://travis-ci.com/typeddjango/django-stubs.svg?branch=master)](https://travis-ci.com/typeddjango/django-stubs)
[![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/) [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
[![Gitter](https://badges.gitter.im/mypy-django/Lobby.svg)](https://gitter.im/mypy-django/Lobby) [![Gitter](https://badges.gitter.im/mypy-django/Lobby.svg)](https://gitter.im/mypy-django/Lobby)
This package contains type stubs and mypy plugin to provide more precise static types and type inference for Django framework. Django uses some Python "magic" that makes having precise types for some code patterns problematic. This is why we need to accompany the stubs with mypy plugins. The final goal is to be able to get precise types for most common patterns. This package contains [type stubs](https://www.python.org/dev/peps/pep-0561/) and a custom mypy plugin to provide more precise static types and type inference for Django framework. Django uses some Python "magic" that makes having precise types for some code patterns problematic. This is why we need this project. The final goal is to be able to get precise types for most common patterns.
Could be run on earlier versions of Django, but expect some missing imports warnings.
## Installation ## Installation
@@ -17,8 +15,35 @@ Could be run on earlier versions of Django, but expect some missing imports warn
pip install django-stubs pip install django-stubs
``` ```
See [Configuration](#configuration) section to get started.
## Mypy compatibility
## Configuration
To make `mypy` happy, you will need to add:
```ini
[mypy]
plugins =
mypy_django_plugin.main
[mypy.plugins.django-stubs]
django_settings_module = "myproject.settings"
```
in your `mypy.ini` or `setup.cfg` [file](https://mypy.readthedocs.io/en/latest/config_file.html).
Two things happeining here:
1. We need to explicitly list our plugin to be loaded by `mypy`
2. Our plugin also requires `django` settings module (what you put into `DJANGO_SETTINGS_MODULE` variable) to be specified
This fully working [typed boilerplate](https://github.com/wemake-services/wemake-django-template) can serve you as an example.
## Version compatibility
We rely on different `django` and `mypy` versions:
| django-stubs | mypy version | django version | python version | django-stubs | mypy version | django version | python version
| ------------ | ---- | ---- | ---- | | ------------ | ---- | ---- | ---- |
@@ -28,58 +53,66 @@ pip install django-stubs
| 0.12.x | old semantic analyzer (<0.711), dmypy support | 2.1.x | ^3.6 | 0.12.x | old semantic analyzer (<0.711), dmypy support | 2.1.x | ^3.6
## Configuration ## FAQ
To make mypy aware of the plugin, you need to add ### Is this an official Django project?
```ini No, it is not. We are indendepent from Django at the moment.
[mypy] There's a [proposal](https://github.com/django/deps/pull/65) to merge our project into the Django itself.
plugins = You show your support by linking the PR.
mypy_django_plugin.main
```
in your `mypy.ini` or `setup.cfg` [file](https://mypy.readthedocs.io/en/latest/config_file.html). ### Is it safe to use this in production?
Plugin also requires Django settings module (what you put into `DJANGO_SETTINGS_MODULE` variable) to be specified. Yes, it is! This project does not affect your runtime at all.
It only affects `mypy` type checking process.
```ini But, it does not make any sense to use this project without `mypy`.
[mypy]
strict_optional = True
# This one is new: ### mypy crashes when I run it with this plugin installed
[mypy.plugins.django-stubs]
django_settings_module = mysettings
```
Where `mysettings` is a value of `DJANGO_SETTINGS_MODULE` (with or without quotes) Current implementation uses Django runtime to extract models information, so it will crash, if your installed apps 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 `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 [`--tb`](https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-show-traceback)
option to get extra information about the error.
This fully working [typed boilerplate](https://github.com/wemake-services/wemake-django-template) can serve you as an example. ### I cannot use QuerySet or Manager with type annotations
You can get a `TypeError: 'type' object is not subscriptable`
when you will try to use `QuerySet[MyModel]` or `Manager[MyModel]`.
## Notes This happens because Django classes do not support [`__class_getitem__`](https://www.python.org/dev/peps/pep-0560/#class-getitem) magic method.
Type implementation monkey-patches Django to add `__class_getitem__` to the `Manager` class. 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.
If you would use Python3.7 and do that too in your code, you can make things like
Currently we [are working](https://github.com/django/django/pull/12405) on providing `__class_getitem__` to the classes where we need them.
### How can I use HttpRequest with custom user model?
You can subclass standard request like so:
```python ```python
class MyUserManager(models.Manager['MyUser']): from django.http import HttpRequest
pass from my_user_app.models import MyUser
class MyUser(models.Model): class MyRequest(HttpRequest):
objects = MyUserManager() user: MyUser
``` ```
work, which should make a error messages a bit better. And then use `MyRequest` instead of standard `HttpRequest` inside your project.
## Related projects
- [`awesome-python-typing`](https://github.com/typeddjango/awesome-python-typing) - Awesome list of all typing-related things in Python.
- [`djangorestframework-stubs`](https://github.com/typeddjango/djangorestframework-stubs) - Stubs for Django REST Framework.
- [`pytest-mypy-plugins`](https://github.com/typeddjango/pytest-mypy-plugins) - `pytest` plugin that we use for testing `mypy` stubs and plugins.
- [`wemake-django-template`](https://github.com/wemake-services/wemake-django-template) - Create new typed Django projects in seconds.
Otherwise, custom type will be created in mypy, named `MyUser__MyUserManager`, which will rewrite base manager as `models.Manager[User]` to make methods like `get_queryset()` and others return properly typed `QuerySet`.
## To get help ## To get help
We have Gitter here: <https://gitter.im/mypy-django/Lobby> We have Gitter here: <https://gitter.im/mypy-django/Lobby>
If you think you have more generic typing issue, please refer to https://github.com/python/mypy and their Gitter. If you think you have more generic typing issue, please refer to <https://github.com/python/mypy> and their Gitter.

View File

@@ -1,8 +1,8 @@
black black
pytest-mypy-plugins==1.1.0 pytest-mypy-plugins==1.2.0
psycopg2 psycopg2
flake8==3.7.9 flake8==3.7.9
flake8-pyi==19.3.0 flake8-pyi==19.3.0
isort==4.3.21 isort==4.3.21
gitpython==3.0.5 gitpython==3.1.0
-e . -e .

View File

@@ -5,10 +5,10 @@ from django.http.response import HttpResponse, HttpResponseBase
from django.urls import URLResolver, URLPattern from django.urls import URLResolver, URLPattern
handler400: Callable[..., HttpResponse] = ... handler400: Union[str, Callable[..., HttpResponse]] = ...
handler403: Callable[..., HttpResponse] = ... handler403: Union[str, Callable[..., HttpResponse]] = ...
handler404: Callable[..., HttpResponse] = ... handler404: Union[str, Callable[..., HttpResponse]] = ...
handler500: Callable[..., HttpResponse] = ... handler500: Union[str, Callable[..., HttpResponse]] = ...
IncludedURLConf = Tuple[List[URLResolver], Optional[str], Optional[str]] IncludedURLConf = Tuple[List[URLResolver], Optional[str], Optional[str]]

View File

@@ -1,13 +1,21 @@
from typing import Callable, List, Optional, Set, Union from typing import Callable, List, Optional, Set, Union, TypeVar, overload
from django.contrib.auth import REDIRECT_FIELD_NAME as REDIRECT_FIELD_NAME # noqa: F401 from django.contrib.auth import REDIRECT_FIELD_NAME as REDIRECT_FIELD_NAME # noqa: F401
from django.http.response import HttpResponseBase
from django.contrib.auth.models import AbstractUser
_VIEW = TypeVar("_VIEW", bound=Callable[..., HttpResponseBase])
def user_passes_test( def user_passes_test(
test_func: Callable, login_url: Optional[str] = ..., redirect_field_name: str = ... test_func: Callable[[AbstractUser], bool], login_url: Optional[str] = ..., redirect_field_name: str = ...
) -> Callable: ... ) -> Callable[[_VIEW], _VIEW]: ...
def login_required(
function: Optional[Callable] = ..., redirect_field_name: str = ..., login_url: Optional[str] = ... # There are two ways of calling @login_required: @with(arguments) and @bare
) -> Callable: ... @overload
def login_required(redirect_field_name: str = ..., login_url: Optional[str] = ...) -> Callable[[_VIEW], _VIEW]: ...
@overload
def login_required(function: _VIEW, redirect_field_name: str = ..., login_url: Optional[str] = ...) -> _VIEW: ...
def permission_required( def permission_required(
perm: Union[List[str], Set[str], str], login_url: None = ..., raise_exception: bool = ... perm: Union[List[str], Set[str], str], login_url: None = ..., raise_exception: bool = ...
) -> Callable: ... ) -> Callable[[_VIEW], _VIEW]: ...

View File

@@ -1,15 +1,14 @@
from datetime import date, datetime as datetime from datetime import date, datetime as datetime
from decimal import Decimal from typing import Any, Optional, SupportsInt, Union
from typing import Any, Optional, Union
register: Any register: Any
def ordinal(value: Optional[str]) -> Optional[str]: ... def ordinal(value: Optional[Union[str, SupportsInt]]) -> Optional[str]: ...
def intcomma(value: Optional[Union[Decimal, float, str]], use_l10n: bool = ...) -> str: ... def intcomma(value: Optional[Union[str, SupportsInt]], use_l10n: bool = ...) -> str: ...
intword_converters: Any intword_converters: Any
def intword(value: Optional[str]) -> Optional[Union[int, str]]: ... def intword(value: Optional[Union[str, SupportsInt]]) -> Optional[Union[int, str]]: ...
def apnumber(value: Optional[str]) -> Optional[Union[int, str]]: ... def apnumber(value: Optional[Union[str, SupportsInt]]) -> Optional[Union[int, str]]: ...
def naturalday(value: Optional[Union[date, str]], arg: None = ...) -> Optional[str]: ... def naturalday(value: Optional[Union[date, str]], arg: None = ...) -> Optional[str]: ...
def naturaltime(value: datetime) -> str: ... def naturaltime(value: datetime) -> str: ...

View File

@@ -1,5 +1,17 @@
from .messages import Warning as Warning, Info as Info, Debug as Debug, Error as Error, Critical as Critical from .messages import (
CheckMessage as CheckMessage,
Debug as Debug,
Info as Info,
Warning as Warning,
Error as Error,
Critical as Critical,
DEBUG as DEBUG,
INFO as INFO,
WARNING as WARNING,
ERROR as ERROR,
CRITICAL as CRITICAL,
)
from .registry import run_checks as run_checks, Tags as Tags, register as register from .registry import register as register, run_checks as run_checks, tag_exists as tag_exists, Tags as Tags
from . import model_checks as model_checks from . import model_checks as model_checks

View File

@@ -7,11 +7,11 @@ ERROR: int
CRITICAL: int CRITICAL: int
class CheckMessage: class CheckMessage:
level: Any = ... level: int = ...
msg: Any = ... msg: str = ...
hint: Any = ... hint: Optional[str] = ...
obj: Any = ... obj: Any = ...
id: Any = ... id: Optional[str] = ...
def __init__( def __init__(
self, level: int, msg: str, hint: Optional[str] = ..., obj: Any = ..., id: Optional[str] = ... self, level: int, msg: str, hint: Optional[str] = ..., obj: Any = ..., id: Optional[str] = ...
) -> None: ... ) -> None: ...
@@ -25,19 +25,9 @@ class Info(CheckMessage):
def __init__(self, *args: Any, **kwargs: Any) -> None: ... def __init__(self, *args: Any, **kwargs: Any) -> None: ...
class Warning(CheckMessage): class Warning(CheckMessage):
hint: str
id: str
level: int
msg: str
obj: Any
def __init__(self, *args: Any, **kwargs: Any) -> None: ... def __init__(self, *args: Any, **kwargs: Any) -> None: ...
class Error(CheckMessage): class Error(CheckMessage):
hint: None
id: str
level: int
msg: str
obj: Any
def __init__(self, *args: Any, **kwargs: Any) -> None: ... def __init__(self, *args: Any, **kwargs: Any) -> None: ...
class Critical(CheckMessage): class Critical(CheckMessage):

View File

@@ -8,7 +8,7 @@ _T = TypeVar("_T", bound="File")
class File(FileProxyMixin, IO[Any]): class File(FileProxyMixin, IO[Any]):
DEFAULT_CHUNK_SIZE: Any = ... DEFAULT_CHUNK_SIZE: Any = ...
file: StringIO = ... file: IO[Any] = ...
name: str = ... name: str = ...
mode: str = ... mode: str = ...
def __init__(self, file: Any, name: Optional[str] = ...) -> None: ... def __init__(self, file: Any, name: Optional[str] = ...) -> None: ...

View File

@@ -23,13 +23,16 @@ class OutputWrapper(TextIOBase):
@property @property
def style_func(self): ... def style_func(self): ...
@style_func.setter @style_func.setter
def style_func(self, style_func: Any): ... def style_func(self, style_func: Callable[[str], str]): ...
ending: str = ... ending: str = ...
def __init__( def __init__(
self, out: Union[StringIO, TextIOWrapper], style_func: Optional[Callable] = ..., ending: str = ... self, out: Union[StringIO, TextIOWrapper], style_func: Optional[Callable[[str], str]] = ..., ending: str = ...
) -> None: ... ) -> None: ...
def __getattr__(self, name: str) -> Callable: ... def __getattr__(self, name: str) -> Callable: ...
def isatty(self) -> bool: ... def isatty(self) -> bool: ...
def write( # type: ignore[override]
self, msg: str, style_func: Optional[Callable[[str], str]] = ..., ending: Optional[str] = ...
) -> None: ...
class BaseCommand: class BaseCommand:
help: str = ... help: str = ...

View File

@@ -21,7 +21,11 @@ def dumps(
obj: Any, key: None = ..., salt: str = ..., serializer: Type[Serializer] = ..., compress: bool = ... obj: Any, key: None = ..., salt: str = ..., serializer: Type[Serializer] = ..., compress: bool = ...
) -> str: ... ) -> str: ...
def loads( def loads(
s: str, key: None = ..., salt: str = ..., serializer: Type[Serializer] = ..., max_age: Optional[int] = ... s: str,
key: None = ...,
salt: str = ...,
serializer: Type[Serializer] = ...,
max_age: Optional[Union[int, timedelta]] = ...,
) -> Any: ... ) -> Any: ...
class Signer: class Signer:

View File

@@ -1,6 +1,6 @@
from decimal import Decimal from decimal import Decimal
from re import RegexFlag from re import RegexFlag
from typing import Any, Dict, List, Optional, Union, Pattern, Collection, Callable, Tuple from typing import Any, Callable, Collection, Dict, List, Optional, Pattern, Tuple, Union
from django.core.files.base import File from django.core.files.base import File
@@ -13,7 +13,7 @@ def _lazy_re_compile(regex: _Regex, flags: int = ...): ...
class RegexValidator: class RegexValidator:
regex: _Regex = ... regex: _Regex = ...
message: Any = ... message: str = ...
code: str = ... code: str = ...
inverse_match: bool = ... inverse_match: bool = ...
flags: int = ... flags: int = ...
@@ -31,24 +31,24 @@ class URLValidator(RegexValidator):
ul: str = ... ul: str = ...
ipv4_re: str = ... ipv4_re: str = ...
ipv6_re: str = ... ipv6_re: str = ...
hostname_re: Any = ... hostname_re: str = ...
domain_re: Any = ... domain_re: str = ...
tld_re: Any = ... tld_re: str = ...
host_re: Any = ... host_re: str = ...
schemes: Any = ... schemes: List[str] = ...
def __init__(self, schemes: Optional[Collection[str]] = ..., **kwargs: Any) -> None: ... def __init__(self, schemes: Optional[Collection[str]] = ..., **kwargs: Any) -> None: ...
integer_validator: Any integer_validator: RegexValidator = ...
def validate_integer(value: Optional[Union[float, str]]) -> None: ... def validate_integer(value: Optional[Union[float, str]]) -> None: ...
class EmailValidator: class EmailValidator:
message: str = ... message: str = ...
code: str = ... code: str = ...
user_regex: Any = ... user_regex: Pattern = ...
domain_regex: Any = ... domain_regex: Pattern = ...
literal_regex: Any = ... literal_regex: Pattern = ...
domain_whitelist: Any = ... domain_whitelist: List[str] = ...
def __init__( def __init__(
self, self,
message: Optional[_ErrorMessage] = ..., message: Optional[_ErrorMessage] = ...,
@@ -68,9 +68,10 @@ def validate_ipv4_address(value: str) -> None: ...
def validate_ipv6_address(value: str) -> None: ... def validate_ipv6_address(value: str) -> None: ...
def validate_ipv46_address(value: str) -> None: ... def validate_ipv46_address(value: str) -> None: ...
ip_address_validator_map: Dict[str, Tuple[Callable[[Any], None], str]] _IPValidator = Tuple[Callable[[Any], None], str]
ip_address_validator_map: Dict[str, _IPValidator]
def ip_address_validators(protocol: str, unpack_ipv4: bool) -> Any: ... def ip_address_validators(protocol: str, unpack_ipv4: bool) -> _IPValidator: ...
def int_list_validator( def int_list_validator(
sep: str = ..., message: Optional[_ErrorMessage] = ..., code: str = ..., allow_negative: bool = ... sep: str = ..., message: Optional[_ErrorMessage] = ..., code: str = ..., allow_negative: bool = ...
) -> RegexValidator: ... ) -> RegexValidator: ...
@@ -92,7 +93,7 @@ class MinLengthValidator(BaseValidator): ...
class MaxLengthValidator(BaseValidator): ... class MaxLengthValidator(BaseValidator): ...
class DecimalValidator: class DecimalValidator:
messages: Any = ... messages: Dict[str, str] = ...
max_digits: int = ... max_digits: int = ...
decimal_places: int = ... decimal_places: int = ...
def __init__(self, max_digits: Optional[Union[int, str]], decimal_places: Optional[Union[int, str]]) -> None: ... def __init__(self, max_digits: Optional[Union[int, str]], decimal_places: Optional[Union[int, str]]) -> None: ...

View File

@@ -108,7 +108,10 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
def set_attributes_from_name(self, name: str) -> None: ... def set_attributes_from_name(self, name: str) -> None: ...
def db_type(self, connection: Any) -> str: ... def db_type(self, connection: Any) -> str: ...
def db_parameters(self, connection: Any) -> Dict[str, str]: ... def db_parameters(self, connection: Any) -> Dict[str, str]: ...
def pre_save(self, model_instance: Model, add: bool) -> Any: ...
def get_prep_value(self, value: Any) -> Any: ... def get_prep_value(self, value: Any) -> Any: ...
def get_db_prep_value(self, value: Any, connection: Any, prepared: bool) -> Any: ...
def get_db_prep_save(self, value: Any, connection: Any) -> Any: ...
def get_internal_type(self) -> str: ... def get_internal_type(self) -> str: ...
# TODO: plugin support # TODO: plugin support
def formfield(self, **kwargs) -> Any: ... def formfield(self, **kwargs) -> Any: ...

View File

@@ -13,6 +13,7 @@ class BaseManager(QuerySet[_T]):
name: str = ... name: str = ...
model: Type[_T] = ... model: Type[_T] = ...
db: str db: str
_db: Optional[str]
def __init__(self) -> None: ... def __init__(self) -> None: ...
def deconstruct(self) -> Tuple[bool, str, None, Tuple, Dict[str, int]]: ... def deconstruct(self) -> Tuple[bool, str, None, Tuple, Dict[str, int]]: ...
def check(self, **kwargs: Any) -> List[Any]: ... def check(self, **kwargs: Any) -> List[Any]: ...

View File

@@ -39,4 +39,11 @@ def atomic(using: _C) -> _C: ...
# Decorator or context-manager with parameters # Decorator or context-manager with parameters
@overload @overload
def atomic(using: Optional[str] = ..., savepoint: bool = ...) -> Atomic: ... def atomic(using: Optional[str] = ..., savepoint: bool = ...) -> Atomic: ...
def non_atomic_requests(using: Callable = ...) -> Callable: ...
# Bare decorator
@overload
def non_atomic_requests(using: _C) -> _C: ...
# Decorator with arguments
@overload
def non_atomic_requests(using: Optional[str] = ...) -> Callable[[_C], _C]: ...

View File

@@ -14,7 +14,7 @@ class Field:
initial: Any initial: Any
label: Optional[str] label: Optional[str]
required: bool required: bool
widget: Widget = ... widget: Type[Widget] = ...
hidden_widget: Any = ... hidden_widget: Any = ...
default_validators: Any = ... default_validators: Any = ...
default_error_messages: Any = ... default_error_messages: Any = ...

View File

@@ -62,25 +62,27 @@ class HttpResponseBase(Iterable[Any]):
def __iter__(self) -> Iterator[Any]: ... def __iter__(self) -> Iterator[Any]: ...
class HttpResponse(HttpResponseBase): class HttpResponse(HttpResponseBase):
client: Client
context: Context
content: Any content: Any
csrf_cookie_set: bool csrf_cookie_set: bool
redirect_chain: List[Tuple[str, int]] redirect_chain: List[Tuple[str, int]]
request: Dict[str, Any]
resolver_match: ResolverMatch
sameorigin: bool sameorigin: bool
templates: List[Template]
test_server_port: str test_server_port: str
test_was_secure_request: bool test_was_secure_request: bool
wsgi_request: WSGIRequest
xframe_options_exempt: bool xframe_options_exempt: bool
streaming: bool = ... streaming: bool = ...
def __init__(self, content: object = ..., *args: Any, **kwargs: Any) -> None: ... def __init__(self, content: object = ..., *args: Any, **kwargs: Any) -> None: ...
def serialize(self) -> bytes: ... def serialize(self) -> bytes: ...
@property @property
def url(self) -> str: ... def url(self) -> str: ...
def json(self) -> Dict[str, Any]: ... # Attributes assigned by monkey-patching in test client ClientHandler.__call__()
wsgi_request: WSGIRequest
# Attributes assigned by monkey-patching in test client Client.request()
client: Client
request: Dict[str, Any]
templates: List[Template]
context: Context
resolver_match: ResolverMatch
def json(self) -> Any: ...
class StreamingHttpResponse(HttpResponseBase): class StreamingHttpResponse(HttpResponseBase):
content: Any content: Any

View File

@@ -1,15 +1,16 @@
from io import BytesIO from io import BytesIO
from types import TracebackType
from typing import Any, Dict, List, Optional, Pattern, Tuple, Type, Union from typing import Any, Dict, List, Optional, Pattern, Tuple, Type, Union
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.contrib.sessions.backends.base import SessionBase from django.contrib.sessions.backends.base import SessionBase
from django.core.handlers.base import BaseHandler from django.core.handlers.base import BaseHandler
from django.core.serializers.json import DjangoJSONEncoder
from django.http.cookie import SimpleCookie from django.http.cookie import SimpleCookie
from django.http.request import HttpRequest from django.http.request import HttpRequest
from django.http.response import HttpResponse, HttpResponseBase from django.http.response import HttpResponse, HttpResponseBase
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
from json import JSONEncoder
BOUNDARY: str = ... BOUNDARY: str = ...
MULTIPART_CONTENT: str = ... MULTIPART_CONTENT: str = ...
@@ -37,11 +38,11 @@ def encode_multipart(boundary: str, data: Dict[str, Any]) -> bytes: ...
def encode_file(boundary: str, key: str, file: Any) -> List[bytes]: ... def encode_file(boundary: str, key: str, file: Any) -> List[bytes]: ...
class RequestFactory: class RequestFactory:
json_encoder: Type[DjangoJSONEncoder] = ... json_encoder: Type[JSONEncoder]
defaults: Dict[str, str] = ... defaults: Dict[str, str]
cookies: SimpleCookie = ... cookies: SimpleCookie
errors: BytesIO = ... errors: BytesIO
def __init__(self, *, json_encoder: Any = ..., **defaults: Any) -> None: ... def __init__(self, *, json_encoder: Type[JSONEncoder] = ..., **defaults: Any) -> None: ...
def request(self, **request: Any) -> WSGIRequest: ... def request(self, **request: Any) -> WSGIRequest: ...
def get(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> WSGIRequest: ... def get(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> WSGIRequest: ...
def post( def post(
@@ -54,6 +55,7 @@ class RequestFactory:
path: str, path: str,
data: Union[Dict[str, str], str] = ..., data: Union[Dict[str, str], str] = ...,
content_type: str = ..., content_type: str = ...,
follow: bool = ...,
secure: bool = ..., secure: bool = ...,
**extra: Any **extra: Any
) -> WSGIRequest: ... ) -> WSGIRequest: ...
@@ -76,39 +78,50 @@ class RequestFactory:
**extra: Any **extra: Any
) -> WSGIRequest: ... ) -> WSGIRequest: ...
class Client: class Client(RequestFactory):
json_encoder: Type[DjangoJSONEncoder] = ... handler: ClientHandler
defaults: Dict[str, str] = ... raise_request_exception: bool
cookies: SimpleCookie = ... exc_info: Optional[Tuple[Type[BaseException], BaseException, TracebackType]]
errors: BytesIO = ... def __init__(
handler: ClientHandler = ... self,
exc_info: None = ... enforce_csrf_checks: bool = ...,
def __init__(self, enforce_csrf_checks: bool = ..., **defaults: Any) -> None: ... raise_request_exception: bool = ...,
def request(self, **request: Any) -> Any: ... *,
def get(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> Any: ... json_encoder: Type[JSONEncoder] = ...,
def post(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ... **defaults: Any
def head(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> Any: ... ) -> None: ...
def trace(self, path: str, secure: bool = ..., **extra: Any) -> Any: ... # Silence type warnings, since this class overrides arguments and return types in an unsafe manner.
def options( def request(self, **request: Any) -> HttpResponse: ... # type: ignore
def get( # type: ignore
self, path: str, data: Any = ..., follow: bool = ..., secure: bool = ..., **extra: Any
) -> HttpResponse: ... # type: ignore
def post( # type: ignore
self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
) -> HttpResponse: ... # type: ignore
def head( # type: ignore
self, path: str, data: Any = ..., follow: bool = ..., secure: bool = ..., **extra: Any
) -> HttpResponse: ... # type: ignore
def trace( # type: ignore
self, path: str, follow: bool = ..., secure: bool = ..., **extra: Any
) -> HttpResponse: ... # type: ignore
def options( # type: ignore
self, self,
path: str, path: str,
data: Union[Dict[str, str], str] = ..., data: Union[Dict[str, str], str] = ...,
content_type: str = ..., content_type: str = ...,
follow: bool = ...,
secure: bool = ..., secure: bool = ...,
**extra: Any **extra: Any
) -> Any: ... ) -> HttpResponse: ... # type: ignore
def put(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ... def put( # type: ignore
def patch(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ... self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
def delete(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ... ) -> HttpResponse: ... # type: ignore
def generic( def patch( # type: ignore
self, self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
method: str, ) -> HttpResponse: ... # type: ignore
path: str, def delete( # type: ignore
data: Any = ..., self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
content_type: Optional[str] = ..., ) -> HttpResponse: ... # type: ignore
secure: bool = ...,
**extra: Any
) -> Any: ...
def store_exc_info(self, **kwargs: Any) -> None: ... def store_exc_info(self, **kwargs: Any) -> None: ...
@property @property
def session(self) -> SessionBase: ... def session(self) -> SessionBase: ...

View File

@@ -16,6 +16,7 @@ from typing import (
Type, Type,
Union, Union,
ContextManager, ContextManager,
TypeVar,
) )
from django.apps.registry import Apps from django.apps.registry import Apps
@@ -29,6 +30,7 @@ from django.conf import LazySettings, Settings
_TestClass = Type[SimpleTestCase] _TestClass = Type[SimpleTestCase]
_DecoratedTest = Union[Callable, _TestClass] _DecoratedTest = Union[Callable, _TestClass]
_C = TypeVar("_C", bound=Callable) # Any callable
TZ_SUPPORT: bool = ... TZ_SUPPORT: bool = ...
@@ -56,7 +58,7 @@ class TestContextDecorator:
def __enter__(self) -> Optional[Apps]: ... def __enter__(self) -> Optional[Apps]: ...
def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None: ... def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None: ...
def decorate_class(self, cls: _TestClass) -> _TestClass: ... def decorate_class(self, cls: _TestClass) -> _TestClass: ...
def decorate_callable(self, func: Callable) -> Callable: ... def decorate_callable(self, func: _C) -> _C: ...
def __call__(self, decorated: _DecoratedTest) -> Any: ... def __call__(self, decorated: _DecoratedTest) -> Any: ...
class override_settings(TestContextDecorator): class override_settings(TestContextDecorator):
@@ -146,7 +148,7 @@ def get_unique_databases_and_mirrors() -> Tuple[Dict[_Signature, _TestDatabase],
def teardown_databases( def teardown_databases(
old_config: Iterable[Tuple[Any, str, bool]], verbosity: int, parallel: int = ..., keepdb: bool = ... old_config: Iterable[Tuple[Any, str, bool]], verbosity: int, parallel: int = ..., keepdb: bool = ...
) -> None: ... ) -> None: ...
def require_jinja2(test_func: Callable) -> Callable: ... def require_jinja2(test_func: _C) -> _C: ...
@contextmanager @contextmanager
def register_lookup( def register_lookup(
field: Type[RegisterLookupMixin], *lookups: Type[Union[Lookup, Transform]], lookup_name: Optional[str] = ... field: Type[RegisterLookupMixin], *lookups: Type[Union[Lookup, Transform]], lookup_name: Optional[str] = ...

View File

@@ -1,7 +1,6 @@
from typing import Any, Optional, Tuple from typing import Any, Optional, Tuple
from django.core.cache.backends.base import BaseCache from django.core.cache.backends.base import BaseCache
from django.core.cache.backends.locmem import LocMemCache
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
from django.http.response import HttpResponse, HttpResponseBase from django.http.response import HttpResponse, HttpResponseBase
@@ -28,5 +27,5 @@ def learn_cache_key(
response: HttpResponse, response: HttpResponse,
cache_timeout: Optional[float] = ..., cache_timeout: Optional[float] = ...,
key_prefix: Optional[str] = ..., key_prefix: Optional[str] = ...,
cache: Optional[LocMemCache] = ..., cache: Optional[BaseCache] = ...,
) -> str: ... ) -> str: ...

View File

@@ -1,13 +1,16 @@
from typing import Any, Callable, Iterable, Optional, Type, Union from typing import Any, Callable, Iterable, Optional, Type, Union, TypeVar
from django.utils.deprecation import MiddlewareMixin from django.utils.deprecation import MiddlewareMixin
from django.views.generic.base import View
_T = TypeVar("_T", bound=Union[View, Callable]) # Any callable
class classonlymethod(classmethod): ... class classonlymethod(classmethod): ...
def method_decorator(decorator: Union[Callable, Iterable[Callable]], name: str = ...) -> Callable: ... def method_decorator(decorator: Union[Callable, Iterable[Callable]], name: str = ...) -> Callable[[_T], _T]: ...
def decorator_from_middleware_with_args(middleware_class: type) -> Callable: ... def decorator_from_middleware_with_args(middleware_class: type) -> Callable: ...
def decorator_from_middleware(middleware_class: type) -> Callable: ... def decorator_from_middleware(middleware_class: type) -> Callable: ...
def available_attrs(fn: Any): ... def available_attrs(fn: Callable): ...
def make_middleware_decorator(middleware_class: Type[MiddlewareMixin]) -> Callable: ... def make_middleware_decorator(middleware_class: Type[MiddlewareMixin]) -> Callable: ...
class classproperty: class classproperty:

View File

@@ -1,16 +1,21 @@
from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union, TypeVar, Generic, overload
from functools import wraps as wraps # noqa: F401 from functools import wraps as wraps # noqa: F401
from django.db.models.base import Model from django.db.models.base import Model
def curry(_curried_func: Any, *args: Any, **kwargs: Any): ... def curry(_curried_func: Any, *args: Any, **kwargs: Any): ...
class cached_property: _T = TypeVar("_T")
func: Callable = ...
class cached_property(Generic[_T]):
func: Callable[..., _T] = ...
__doc__: Any = ... __doc__: Any = ...
name: str = ... name: str = ...
def __init__(self, func: Callable, name: Optional[str] = ...) -> None: ... def __init__(self, func: Callable[..., _T], name: Optional[str] = ...): ...
def __get__(self, instance: Any, cls: Type[Any] = ...) -> Any: ... @overload
def __get__(self, instance: None, cls: Type[Any] = ...) -> "cached_property[_T]": ...
@overload
def __get__(self, instance: object, cls: Type[Any] = ...) -> _T: ...
class Promise: ... class Promise: ...

View File

@@ -10,8 +10,8 @@ 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, StrExpr, SymbolNode, GDEF, MDEF, Argument, Block, ClassDef, Expression, FuncDef, MemberExpr, MypyFile, NameExpr, PlaceholderNode,
SymbolTable, SymbolTableNode, TypeInfo, Var, 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,
@@ -309,34 +309,67 @@ def add_new_sym_for_info(info: TypeInfo, *, name: str, sym_type: MypyType) -> No
plugin_generated=True) plugin_generated=True)
def _prepare_new_method_arguments(node: FuncDef) -> Tuple[List[Argument], MypyType]: def build_unannotated_method_args(method_node: FuncDef) -> Tuple[List[Argument], MypyType]:
arguments = [] prepared_arguments = []
for argument in node.arguments[1:]: for argument in method_node.arguments[1:]:
if argument.type_annotation is None: argument.type_annotation = AnyType(TypeOfAny.unannotated)
argument.type_annotation = AnyType(TypeOfAny.unannotated) prepared_arguments.append(argument)
arguments.append(argument) return_type = AnyType(TypeOfAny.unannotated)
return prepared_arguments, return_type
if isinstance(node.type, CallableType):
return_type = node.type.ret_type
else:
return_type = AnyType(TypeOfAny.unannotated)
return arguments, return_type
def copy_method_to_another_class(ctx: ClassDefContext, self_type: Instance, def copy_method_to_another_class(ctx: ClassDefContext, self_type: Instance,
new_method_name: str, method_node: FuncDef) -> None: new_method_name: str, method_node: FuncDef) -> None:
arguments, return_type = _prepare_new_method_arguments(method_node)
semanal_api = get_semanal_api(ctx) semanal_api = get_semanal_api(ctx)
for argument in arguments: if method_node.type is None:
if argument.type_annotation is not None: if not semanal_api.final_iteration:
argument.type_annotation = semanal_api.anal_type(argument.type_annotation) semanal_api.defer()
if return_type is not None: return
return_type = semanal_api.anal_type(return_type) or AnyType(TypeOfAny.unannotated)
arguments, return_type = build_unannotated_method_args(method_node)
add_method(ctx,
new_method_name,
args=arguments,
return_type=return_type,
self_type=self_type)
return
method_type = method_node.type
if not isinstance(method_type, CallableType):
if not semanal_api.final_iteration:
semanal_api.defer()
return
arguments = []
bound_return_type = semanal_api.anal_type(method_type.ret_type,
allow_placeholder=True)
assert bound_return_type is not None
if isinstance(bound_return_type, PlaceholderNode):
return
for arg_name, arg_type, original_argument in zip(method_type.arg_names[1:],
method_type.arg_types[1:],
method_node.arguments[1:]):
bound_arg_type = semanal_api.anal_type(arg_type, allow_placeholder=True)
assert bound_arg_type is not None
if isinstance(bound_arg_type, PlaceholderNode):
return
var = Var(name=original_argument.variable.name,
type=arg_type)
var.line = original_argument.variable.line
var.column = original_argument.variable.column
argument = Argument(variable=var,
type_annotation=bound_arg_type,
initializer=original_argument.initializer,
kind=original_argument.kind)
argument.set_line(original_argument)
arguments.append(argument)
add_method(ctx, add_method(ctx,
new_method_name, new_method_name,
args=arguments, args=arguments,
return_type=return_type, return_type=bound_return_type,
self_type=self_type) self_type=self_type)

View File

@@ -64,7 +64,12 @@ IGNORED_ERRORS = {
'Incompatible types in string interpolation', 'Incompatible types in string interpolation',
'"None" has no attribute', '"None" has no attribute',
'has no attribute "assert', 'has no attribute "assert',
'Unsupported dynamic base class' 'Unsupported dynamic base class',
'error: "HttpResponse" has no attribute "streaming_content"',
'error: "HttpResponse" has no attribute "context_data"',
],
'admin_inlines': [
'error: "HttpResponse" has no attribute "rendered_content"',
], ],
'admin_utils': [ 'admin_utils': [
'"Article" has no attribute "non_field"', '"Article" has no attribute "non_field"',
@@ -104,6 +109,7 @@ IGNORED_ERRORS = {
], ],
'builtin_server': [ 'builtin_server': [
'"ServerHandler" has no attribute', '"ServerHandler" has no attribute',
'Incompatible types in assignment (expression has type "Tuple[BytesIO, BytesIO]"',
], ],
'bulk_create': [ 'bulk_create': [
'has incompatible type "List[Country]"; expected "Iterable[TwoFields]"', 'has incompatible type "List[Country]"; expected "Iterable[TwoFields]"',
@@ -164,7 +170,8 @@ IGNORED_ERRORS = {
'Incompatible types in assignment (expression has type "Optional[Any]", variable has type "FloatModel")' 'Incompatible types in assignment (expression has type "Optional[Any]", variable has type "FloatModel")'
], ],
'decorators': [ 'decorators': [
'"Type[object]" has no attribute "method"' '"Type[object]" has no attribute "method"',
'Value of type variable "_T" of function cannot be "descriptor_wrapper"'
], ],
'expressions_window': [ 'expressions_window': [
'has incompatible type "str"' 'has incompatible type "str"'
@@ -178,7 +185,6 @@ IGNORED_ERRORS = {
], ],
'files': [ 'files': [
'Incompatible types in assignment (expression has type "IOBase", variable has type "File")', 'Incompatible types in assignment (expression has type "IOBase", variable has type "File")',
'Argument 1 to "write" of "SpooledTemporaryFile"',
], ],
'filtered_relation': [ 'filtered_relation': [
'has no attribute "name"', 'has no attribute "name"',
@@ -192,7 +198,7 @@ IGNORED_ERRORS = {
], ],
'from_db_value': [ 'from_db_value': [
'"Cash" has no attribute', '"Cash" has no attribute',
'Argument 1 to "__str__" of "Decimal"', '"__str__" of "Decimal"',
], ],
'get_object_or_404': [ 'get_object_or_404': [
'Argument 1 to "get_object_or_404" has incompatible type "str"; ' 'Argument 1 to "get_object_or_404" has incompatible type "str"; '
@@ -231,13 +237,8 @@ IGNORED_ERRORS = {
], ],
'mail': [ 'mail': [
'List item 1 has incompatible type "None"; expected "str"', 'List item 1 has incompatible type "None"; expected "str"',
'Argument 1 to "push" of "SMTPChannel" has incompatible type "str"; expected "bytes"',
'Value of type "Union[List[Message], str, bytes, None]" is not indexable',
'Incompatible types in assignment ' 'Incompatible types in assignment '
+ '(expression has type "bool", variable has type "Union[SMTP_SSL, SMTP, None]")', + '(expression has type "bool", variable has type "Union[SMTP_SSL, SMTP, None]")',
re.compile(
r'Item "(int|str)" of "Union\[Message, str, int, Any\]" has no attribute "(get_content_type|get_filename)"'
)
], ],
'messages_tests': [ 'messages_tests': [
'List item 0 has incompatible type "Dict[str, Message]"; expected "Message"', 'List item 0 has incompatible type "Dict[str, Message]"; expected "Message"',
@@ -246,6 +247,7 @@ IGNORED_ERRORS = {
], ],
'middleware': [ 'middleware': [
re.compile(r'"(HttpRequest|WSGIRequest)" has no attribute'), re.compile(r'"(HttpRequest|WSGIRequest)" has no attribute'),
'Incompatible types in assignment (expression has type "HttpResponseBase", variable has type "HttpResponse")',
], ],
'many_to_many': [ 'many_to_many': [
'(expression has type "List[Article]", variable has type "Article_RelatedManager2', '(expression has type "List[Article]", variable has type "Article_RelatedManager2',
@@ -282,6 +284,7 @@ IGNORED_ERRORS = {
'"ImageFile" has no attribute "was_opened"', '"ImageFile" has no attribute "was_opened"',
'Incompatible type for "size" of "FloatModel" (got "object", expected "Union[float, int, str, Combinable]")', 'Incompatible type for "size" of "FloatModel" (got "object", expected "Union[float, int, str, Combinable]")',
'Incompatible type for "value" of "IntegerModel" (got "object", expected', 'Incompatible type for "value" of "IntegerModel" (got "object", expected',
'"Child" has no attribute "get_foo_display"',
], ],
'model_forms': [ 'model_forms': [
'"render" of "Widget"', '"render" of "Widget"',
@@ -320,9 +323,6 @@ IGNORED_ERRORS = {
'model_enums': [ 'model_enums': [
"'bool' is not a valid base class", "'bool' is not a valid base class",
], ],
'multiple_database': [
'Unexpected attribute "extra_arg" for model "Book"'
],
'null_queries': [ 'null_queries': [
"Cannot resolve keyword 'foo' into field" "Cannot resolve keyword 'foo' into field"
], ],
@@ -385,6 +385,9 @@ IGNORED_ERRORS = {
'settings_tests': [ 'settings_tests': [
'Argument 1 to "Settings" has incompatible type "Optional[str]"; expected "str"' 'Argument 1 to "Settings" has incompatible type "Optional[str]"; expected "str"'
], ],
'shortcuts': [
'error: "Context" has no attribute "request"',
],
'signals': [ 'signals': [
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any, Optional[Any], Any]";' 'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any, Optional[Any], Any]";'
], ],
@@ -396,7 +399,7 @@ IGNORED_ERRORS = {
'"RequestSite" of "Union[Site, RequestSite]" has no attribute "id"', '"RequestSite" of "Union[Site, RequestSite]" has no attribute "id"',
], ],
'syndication_tests': [ 'syndication_tests': [
'List or tuple expected as variable arguments' 'Argument 1 to "add_domain" has incompatible type "*Tuple[object, ...]"',
], ],
'sessions_tests': [ 'sessions_tests': [
'Incompatible types in assignment (expression has type "None", variable has type "int")', 'Incompatible types in assignment (expression has type "None", variable has type "int")',
@@ -437,6 +440,7 @@ IGNORED_ERRORS = {
'test_client_regress': [ 'test_client_regress': [
'(expression has type "Dict[<nothing>, <nothing>]", variable has type "SessionBase")', '(expression has type "Dict[<nothing>, <nothing>]", variable has type "SessionBase")',
'Unsupported left operand type for + ("None")', 'Unsupported left operand type for + ("None")',
'Argument 1 to "len" has incompatible type "Context"; expected "Sized"',
], ],
'transactions': [ 'transactions': [
'Incompatible types in assignment (expression has type "Thread", variable has type "Callable[[], Any]")' 'Incompatible types in assignment (expression has type "Thread", variable has type "Callable[[], Any]")'
@@ -466,7 +470,7 @@ IGNORED_ERRORS = {
'No overload variant of "join" matches argument types "str", "None"', 'No overload variant of "join" matches argument types "str", "None"',
'Argument 1 to "Archive" has incompatible type "None"; expected "str"', 'Argument 1 to "Archive" has incompatible type "None"; expected "str"',
'Argument 1 to "to_path" has incompatible type "int"; expected "Union[Path, str]"', 'Argument 1 to "to_path" has incompatible type "int"; expected "Union[Path, str]"',
'Cannot infer type argument 1 of "cached_property"',
], ],
'view_tests': [ 'view_tests': [
"Module 'django.views.debug' has no attribute 'Path'", "Module 'django.views.debug' has no attribute 'Path'",

View File

@@ -14,8 +14,8 @@ from scripts.enabled_test_modules import (
) )
DJANGO_COMMIT_REFS: Dict[str, Tuple[str, str]] = { DJANGO_COMMIT_REFS: Dict[str, Tuple[str, str]] = {
'2.2': ('stable/2.2.x', '86befcc172c23170a720b3e0c06db51a99b3da59'), '2.2': ('stable/2.2.x', '996be04c3ceb456754d9d527d4d708f30727f07e'),
'3.0': ('stable/3.0.x', '6cb30414bc0f83b49afc4cae76d4af5656effe9a') '3.0': ('stable/3.0.x', 'd9f1792c7649e9f946f4a3a35a76bddf5a412b8b')
} }
PROJECT_DIRECTORY = Path(__file__).parent.parent PROJECT_DIRECTORY = Path(__file__).parent.parent
DJANGO_SOURCE_DIRECTORY = PROJECT_DIRECTORY / 'django-sources' # type: Path DJANGO_SOURCE_DIRECTORY = PROJECT_DIRECTORY / 'django-sources' # type: Path

View File

@@ -21,14 +21,14 @@ with open('README.md', 'r') as f:
readme = f.read() readme = f.read()
dependencies = [ dependencies = [
'mypy>=0.750,<0.760', 'mypy>=0.770,<0.780',
'typing-extensions', 'typing-extensions',
'django', 'django',
] ]
setup( setup(
name="django-stubs", name="django-stubs",
version="1.3.3", version="1.5.0",
description='Mypy stubs for Django', description='Mypy stubs for Django',
long_description=readme, long_description=readme,
long_description_content_type='text/markdown', long_description_content_type='text/markdown',

View File

@@ -0,0 +1,43 @@
- case: login_required_bare
main: |
from django.contrib.auth.decorators import login_required
@login_required
def view_func(request): ...
reveal_type(view_func) # N: Revealed type is 'def (request: Any) -> Any'
- case: login_required_fancy
main: |
from django.contrib.auth.decorators import login_required
from django.core.handlers.wsgi import WSGIRequest
from django.http import HttpResponse
@login_required(redirect_field_name='a', login_url='b')
def view_func(request: WSGIRequest, arg: str) -> HttpResponse: ...
reveal_type(view_func) # N: Revealed type is 'def (request: django.core.handlers.wsgi.WSGIRequest, arg: builtins.str) -> django.http.response.HttpResponse'
- case: login_required_weird
main: |
from django.contrib.auth.decorators import login_required
# This is non-conventional usage, but covered in Django tests, so we allow it.
def view_func(request): ...
wrapped_view = login_required(view_func, redirect_field_name='a', login_url='b')
reveal_type(wrapped_view) # N: Revealed type is 'def (request: Any) -> Any'
- case: login_required_incorrect_return
main: |
from django.contrib.auth.decorators import login_required
@login_required() # E: Value of type variable "_VIEW" of function cannot be "Callable[[Any], str]"
def view_func2(request) -> str: ...
- case: user_passes_test
main: |
from django.contrib.auth.decorators import user_passes_test
@user_passes_test(lambda u: u.username.startswith('super'))
def view_func(request): ...
reveal_type(view_func) # N: Revealed type is 'def (request: Any) -> Any'
- case: user_passes_test_bare_is_error
main: |
from django.http.response import HttpResponse
from django.contrib.auth.decorators import user_passes_test
@user_passes_test # E: Argument 1 to "user_passes_test" has incompatible type "Callable[[Any], HttpResponse]"; expected "Callable[[AbstractUser], bool]"
def view_func(request) -> HttpResponse: ...
- case: permission_required
main: |
from django.contrib.auth.decorators import permission_required
@permission_required('polls.can_vote')
def view_func(request): ...

View File

@@ -0,0 +1,28 @@
- case: atomic_bare
main: |
from django.db.transaction import atomic
@atomic
def func(x: int) -> list: ...
reveal_type(func) # N: Revealed type is 'def (x: builtins.int) -> builtins.list[Any]'
- case: atomic_args
main: |
from django.db.transaction import atomic
@atomic(using='bla', savepoint=False)
def func(x: int) -> list: ...
reveal_type(func) # N: Revealed type is 'def (x: builtins.int) -> builtins.list[Any]'
- case: non_atomic_requests_bare
main: |
from django.db.transaction import non_atomic_requests
@non_atomic_requests
def view_func(request): ...
reveal_type(view_func) # N: Revealed type is 'def (request: Any) -> Any'
- case: non_atomic_requests_args
main: |
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.db.transaction import non_atomic_requests
@non_atomic_requests
def view_func(request: HttpRequest, arg: str) -> HttpResponse: ...
reveal_type(view_func) # N: Revealed type is 'def (request: django.http.request.HttpRequest, arg: builtins.str) -> django.http.response.HttpResponse'

View File

@@ -146,3 +146,36 @@
return param return param
NewManager = models.Manager.from_queryset(ModelQuerySet) NewManager = models.Manager.from_queryset(ModelQuerySet)
- case: from_queryset_with_inherited_manager_and_typing_no_return
disable_cache: true
main: |
from myapp.models import MyModel
reveal_type(MyModel().objects) # N: Revealed type is 'myapp.models.MyModel_NewManager[myapp.models.MyModel]'
reveal_type(MyModel().objects.get()) # N: Revealed type is 'myapp.models.MyModel*'
reveal_type(MyModel().objects.base_queryset_method) # N: Revealed type is 'def (param: Union[builtins.int, builtins.str]) -> <nothing>'
reveal_type(MyModel().objects.base_queryset_method(2)) # N: Revealed type is '<nothing>'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
from myapp.managers import NewManager
class MyModel(models.Model):
objects = NewManager()
- path: myapp/managers.py
content: |
from django.db import models
from myapp.base_queryset import BaseQuerySet
class ModelQuerySet(BaseQuerySet):
pass
NewManager = models.Manager.from_queryset(ModelQuerySet)
- path: myapp/base_queryset.py
content: |
from typing import NoReturn, Union
from django.db import models
class BaseQuerySet(models.QuerySet):
def base_queryset_method(self, param: Union[int, str]) -> NoReturn:
raise ValueError

View File

@@ -307,15 +307,15 @@
- case: custom_manager_returns_proper_model_types - case: custom_manager_returns_proper_model_types
main: | main: |
from myapp.models import User from myapp.models import User
reveal_type(User.objects) # N: Revealed type is 'myapp.models.User_MyManager[myapp.models.User]' reveal_type(User.objects) # N: Revealed type is 'myapp.models.User_MyManager2[myapp.models.User]'
reveal_type(User.objects.select_related()) # N: Revealed type is 'myapp.models.User_MyManager[myapp.models.User]' reveal_type(User.objects.select_related()) # N: Revealed type is 'myapp.models.User_MyManager2[myapp.models.User]'
reveal_type(User.objects.get()) # N: Revealed type is 'myapp.models.User*' reveal_type(User.objects.get()) # N: Revealed type is 'myapp.models.User*'
reveal_type(User.objects.get_instance()) # N: Revealed type is 'builtins.int' reveal_type(User.objects.get_instance()) # N: Revealed type is 'builtins.int'
reveal_type(User.objects.get_instance_untyped('hello')) # N: Revealed type is 'Any' reveal_type(User.objects.get_instance_untyped('hello')) # N: Revealed type is 'Any'
from myapp.models import ChildUser from myapp.models import ChildUser
reveal_type(ChildUser.objects) # N: Revealed type is 'myapp.models.ChildUser_MyManager[myapp.models.ChildUser]' reveal_type(ChildUser.objects) # N: Revealed type is 'myapp.models.ChildUser_MyManager2[myapp.models.ChildUser]'
reveal_type(ChildUser.objects.select_related()) # N: Revealed type is 'myapp.models.ChildUser_MyManager[myapp.models.ChildUser]' reveal_type(ChildUser.objects.select_related()) # N: Revealed type is 'myapp.models.ChildUser_MyManager2[myapp.models.ChildUser]'
reveal_type(ChildUser.objects.get()) # N: Revealed type is 'myapp.models.ChildUser*' reveal_type(ChildUser.objects.get()) # N: Revealed type is 'myapp.models.ChildUser*'
reveal_type(ChildUser.objects.get_instance()) # N: Revealed type is 'builtins.int' reveal_type(ChildUser.objects.get_instance()) # N: Revealed type is 'builtins.int'
reveal_type(ChildUser.objects.get_instance_untyped('hello')) # N: Revealed type is 'Any' reveal_type(ChildUser.objects.get_instance_untyped('hello')) # N: Revealed type is 'Any'

View File

@@ -0,0 +1,12 @@
- case: testcase_client_attr
main: |
from django.test.testcases import TestCase
class ExampleTestCase(TestCase):
def test_method(self) -> None:
reveal_type(self.client) # N: Revealed type is 'django.test.client.Client'
resp = self.client.post('/url', {'doit': 'srs'}, 'application/json', False, True, extra='value')
reveal_type(resp.status_code) # N: Revealed type is 'builtins.int'
# Attributes monkey-patched by test Client class:
resp.json()
reveal_type(resp.wsgi_request) # N: Revealed type is 'django.core.handlers.wsgi.WSGIRequest'

View File

@@ -0,0 +1,20 @@
- case: method_decorator_class
main: |
from django.views.generic.base import View
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
@method_decorator(login_required, name='dispatch')
class TestView(View): ...
reveal_type(TestView()) # N: Revealed type is 'main.TestView'
- case: method_decorator_function
main: |
from django.views.generic.base import View
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.http.response import HttpResponse
from django.http.request import HttpRequest
class TestView(View):
@method_decorator(login_required)
def dispatch(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
return super().dispatch(request, *args, **kwargs)
reveal_type(dispatch) # N: Revealed type is 'def (self: main.TestView, request: django.http.request.HttpRequest, *args: Any, **kwargs: Any) -> django.http.response.HttpResponse'

View File

@@ -0,0 +1,18 @@
- case: cached_property_class_vs_instance_attributes
main: |
from django.utils.functional import cached_property
from typing import List
class Foo:
@cached_property
def attr(self) -> List[str]: ...
reveal_type(attr) # N: Revealed type is 'django.utils.functional.cached_property[builtins.list*[builtins.str]]'
reveal_type(attr.name) # N: Revealed type is 'builtins.str'
reveal_type(Foo.attr) # N: Revealed type is 'django.utils.functional.cached_property[builtins.list*[builtins.str]]'
reveal_type(Foo.attr.func) # N: Revealed type is 'def (*Any, **Any) -> builtins.list*[builtins.str]'
f = Foo()
reveal_type(f.attr) # N: Revealed type is 'builtins.list*[builtins.str]'
f.attr.name # E: "List[str]" has no attribute "name"