15 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
20 changed files with 347 additions and 79 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.

View File

@@ -15,7 +15,7 @@ This package contains [type stubs](https://www.python.org/dev/peps/pep-0561/) an
pip install django-stubs pip install django-stubs
``` ```
See [Configutation](#configuration) section to get started. See [Configuration](#configuration) section to get started.
## Configuration ## Configuration
@@ -55,39 +55,39 @@ We rely on different `django` and `mypy` versions:
## FAQ ## FAQ
> Is this an official Django project? ### Is this an official Django project?
No, it is not. We are indendepent from Django at the moment. No, it is not. We are indendepent from Django at the moment.
There's a [proposal](https://github.com/django/deps/pull/65) to merge our project into the Django itself. 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 show your support by linking the PR.
> Is it safe to use this in production? ### Is it safe to use this in production?
Yes, it is! This project does not affect your runtime at all. Yes, it is! This project does not affect your runtime at all.
It only affects `mypy` type checking process. It only affects `mypy` type checking process.
But, it does not make sense to use this project without `mypy`. 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
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()`. 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 [`--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 the error. option to get extra information about the error.
> I cannot use QuerySet or Manager with type annotations ### I cannot use QuerySet or Manager with type annotations
You can get a `TypeError: 'type' object is not subscriptable` You can get a `TypeError: 'type' object is not subscriptable`
when you will try to use `QuerySet[MyModel]` or `Manager[MyModel]`. when you will try to use `QuerySet[MyModel]` or `Manager[MyModel]`.
This happens because Django classes do not support [`__class_getitem__`](https://www.python.org/dev/peps/pep-0560/#class-getitem) magic method. This happens because Django classes do not support [`__class_getitem__`](https://www.python.org/dev/peps/pep-0560/#class-getitem) magic method.
There are several things 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. You can use strings instead: `'QuerySet[MyModel]'` and `'Manager[MyModel]'`, this way it will work as a type for `mypy` and as a regular `str` in runtime.
Currently we [are working](https://github.com/django/django/pull/12405) on providing `__class_getitem__` to the classes where we need them. 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? ### How can I use HttpRequest with custom user model?
You can subclass standard request like so: You can subclass standard request like so:

View File

@@ -4,5 +4,5 @@ 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

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

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

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

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

@@ -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"'
@@ -191,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"; '
@@ -240,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',
@@ -276,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"',
@@ -376,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]";'
], ],
@@ -387,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")',
@@ -428,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]")'

View File

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

View File

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

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

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