mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-10 22:11:54 +08:00
Compare commits
15 Commits
new-readme
...
v1.5.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1af3a12f2c | ||
|
|
7af89ee6a6 | ||
|
|
afa16bfb74 | ||
|
|
f77073157b | ||
|
|
fe3b95c611 | ||
|
|
d0f9730c53 | ||
|
|
0fdd678d65 | ||
|
|
2397065fa6 | ||
|
|
04023a9f31 | ||
|
|
95e6c94319 | ||
|
|
d96aee7a8b | ||
|
|
2489bb9b04 | ||
|
|
3a8f278c88 | ||
|
|
85b65b4578 | ||
|
|
150e8e862a |
111
CONTRIBUTING.md
Normal file
111
CONTRIBUTING.md
Normal 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.
|
||||||
18
README.md
18
README.md
@@ -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:
|
||||||
|
|
||||||
|
|||||||
@@ -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 .
|
||||||
|
|||||||
@@ -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]: ...
|
||||||
|
|||||||
@@ -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: ...
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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: ...
|
||||||
|
|||||||
@@ -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]: ...
|
||||||
|
|||||||
@@ -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 = ...
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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: ...
|
||||||
|
|||||||
@@ -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] = ...
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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]")'
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
4
setup.py
4
setup.py
@@ -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',
|
||||||
|
|||||||
43
test-data/typecheck/contrib/auth/test_decorators.yml
Normal file
43
test-data/typecheck/contrib/auth/test_decorators.yml
Normal 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): ...
|
||||||
28
test-data/typecheck/db/test_transaction.yml
Normal file
28
test-data/typecheck/db/test_transaction.yml
Normal 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'
|
||||||
|
|
||||||
12
test-data/typecheck/test/test_testcase.yml
Normal file
12
test-data/typecheck/test/test_testcase.yml
Normal 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'
|
||||||
20
test-data/typecheck/utils/test_decorators.yml
Normal file
20
test-data/typecheck/utils/test_decorators.yml
Normal 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'
|
||||||
Reference in New Issue
Block a user