mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-10 14:01:56 +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
|
||||
```
|
||||
|
||||
See [Configutation](#configuration) section to get started.
|
||||
See [Configuration](#configuration) section to get started.
|
||||
|
||||
|
||||
## Configuration
|
||||
@@ -55,39 +55,39 @@ We rely on different `django` and `mypy` versions:
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
> 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.
|
||||
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.
|
||||
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.
|
||||
|
||||
> 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`
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
> 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:
|
||||
|
||||
|
||||
@@ -4,5 +4,5 @@ psycopg2
|
||||
flake8==3.7.9
|
||||
flake8-pyi==19.3.0
|
||||
isort==4.3.21
|
||||
gitpython==3.0.5
|
||||
gitpython==3.1.0
|
||||
-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.http.response import HttpResponseBase
|
||||
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
|
||||
_VIEW = TypeVar("_VIEW", bound=Callable[..., HttpResponseBase])
|
||||
|
||||
def user_passes_test(
|
||||
test_func: Callable, login_url: Optional[str] = ..., redirect_field_name: str = ...
|
||||
) -> Callable: ...
|
||||
def login_required(
|
||||
function: Optional[Callable] = ..., redirect_field_name: str = ..., login_url: Optional[str] = ...
|
||||
) -> Callable: ...
|
||||
test_func: Callable[[AbstractUser], bool], login_url: Optional[str] = ..., redirect_field_name: str = ...
|
||||
) -> Callable[[_VIEW], _VIEW]: ...
|
||||
|
||||
# There are two ways of calling @login_required: @with(arguments) and @bare
|
||||
@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(
|
||||
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 decimal import Decimal
|
||||
from typing import Any, Optional, Union
|
||||
from typing import Any, Optional, SupportsInt, Union
|
||||
|
||||
register: Any
|
||||
|
||||
def ordinal(value: Optional[str]) -> Optional[str]: ...
|
||||
def intcomma(value: Optional[Union[Decimal, float, str]], use_l10n: bool = ...) -> str: ...
|
||||
def ordinal(value: Optional[Union[str, SupportsInt]]) -> Optional[str]: ...
|
||||
def intcomma(value: Optional[Union[str, SupportsInt]], use_l10n: bool = ...) -> str: ...
|
||||
|
||||
intword_converters: Any
|
||||
|
||||
def intword(value: Optional[str]) -> Optional[Union[int, str]]: ...
|
||||
def apnumber(value: Optional[str]) -> Optional[Union[int, str]]: ...
|
||||
def intword(value: Optional[Union[str, SupportsInt]]) -> Optional[Union[int, str]]: ...
|
||||
def apnumber(value: Optional[Union[str, SupportsInt]]) -> Optional[Union[int, str]]: ...
|
||||
def naturalday(value: Optional[Union[date, str]], arg: None = ...) -> Optional[str]: ...
|
||||
def naturaltime(value: datetime) -> str: ...
|
||||
|
||||
@@ -21,7 +21,11 @@ def dumps(
|
||||
obj: Any, key: None = ..., salt: str = ..., serializer: Type[Serializer] = ..., compress: bool = ...
|
||||
) -> str: ...
|
||||
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: ...
|
||||
|
||||
class Signer:
|
||||
|
||||
@@ -108,7 +108,10 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
|
||||
def set_attributes_from_name(self, name: str) -> None: ...
|
||||
def db_type(self, connection: Any) -> 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_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: ...
|
||||
# TODO: plugin support
|
||||
def formfield(self, **kwargs) -> Any: ...
|
||||
|
||||
@@ -39,4 +39,11 @@ def atomic(using: _C) -> _C: ...
|
||||
# Decorator or context-manager with parameters
|
||||
@overload
|
||||
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
|
||||
label: Optional[str]
|
||||
required: bool
|
||||
widget: Widget = ...
|
||||
widget: Type[Widget] = ...
|
||||
hidden_widget: Any = ...
|
||||
default_validators: Any = ...
|
||||
default_error_messages: Any = ...
|
||||
|
||||
@@ -62,25 +62,27 @@ class HttpResponseBase(Iterable[Any]):
|
||||
def __iter__(self) -> Iterator[Any]: ...
|
||||
|
||||
class HttpResponse(HttpResponseBase):
|
||||
client: Client
|
||||
context: Context
|
||||
content: Any
|
||||
csrf_cookie_set: bool
|
||||
redirect_chain: List[Tuple[str, int]]
|
||||
request: Dict[str, Any]
|
||||
resolver_match: ResolverMatch
|
||||
sameorigin: bool
|
||||
templates: List[Template]
|
||||
test_server_port: str
|
||||
test_was_secure_request: bool
|
||||
wsgi_request: WSGIRequest
|
||||
xframe_options_exempt: bool
|
||||
streaming: bool = ...
|
||||
def __init__(self, content: object = ..., *args: Any, **kwargs: Any) -> None: ...
|
||||
def serialize(self) -> bytes: ...
|
||||
@property
|
||||
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):
|
||||
content: Any
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
from io import BytesIO
|
||||
from types import TracebackType
|
||||
from typing import Any, Dict, List, Optional, Pattern, Tuple, Type, Union
|
||||
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.contrib.sessions.backends.base import SessionBase
|
||||
from django.core.handlers.base import BaseHandler
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.http.cookie import SimpleCookie
|
||||
from django.http.request import HttpRequest
|
||||
from django.http.response import HttpResponse, HttpResponseBase
|
||||
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
from json import JSONEncoder
|
||||
|
||||
BOUNDARY: 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]: ...
|
||||
|
||||
class RequestFactory:
|
||||
json_encoder: Type[DjangoJSONEncoder] = ...
|
||||
defaults: Dict[str, str] = ...
|
||||
cookies: SimpleCookie = ...
|
||||
errors: BytesIO = ...
|
||||
def __init__(self, *, json_encoder: Any = ..., **defaults: Any) -> None: ...
|
||||
json_encoder: Type[JSONEncoder]
|
||||
defaults: Dict[str, str]
|
||||
cookies: SimpleCookie
|
||||
errors: BytesIO
|
||||
def __init__(self, *, json_encoder: Type[JSONEncoder] = ..., **defaults: Any) -> None: ...
|
||||
def request(self, **request: Any) -> WSGIRequest: ...
|
||||
def get(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> WSGIRequest: ...
|
||||
def post(
|
||||
@@ -54,6 +55,7 @@ class RequestFactory:
|
||||
path: str,
|
||||
data: Union[Dict[str, str], str] = ...,
|
||||
content_type: str = ...,
|
||||
follow: bool = ...,
|
||||
secure: bool = ...,
|
||||
**extra: Any
|
||||
) -> WSGIRequest: ...
|
||||
@@ -76,39 +78,50 @@ class RequestFactory:
|
||||
**extra: Any
|
||||
) -> WSGIRequest: ...
|
||||
|
||||
class Client:
|
||||
json_encoder: Type[DjangoJSONEncoder] = ...
|
||||
defaults: Dict[str, str] = ...
|
||||
cookies: SimpleCookie = ...
|
||||
errors: BytesIO = ...
|
||||
handler: ClientHandler = ...
|
||||
exc_info: None = ...
|
||||
def __init__(self, enforce_csrf_checks: bool = ..., **defaults: Any) -> None: ...
|
||||
def request(self, **request: Any) -> Any: ...
|
||||
def get(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
||||
def post(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
||||
def head(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
||||
def trace(self, path: str, secure: bool = ..., **extra: Any) -> Any: ...
|
||||
def options(
|
||||
class Client(RequestFactory):
|
||||
handler: ClientHandler
|
||||
raise_request_exception: bool
|
||||
exc_info: Optional[Tuple[Type[BaseException], BaseException, TracebackType]]
|
||||
def __init__(
|
||||
self,
|
||||
enforce_csrf_checks: bool = ...,
|
||||
raise_request_exception: bool = ...,
|
||||
*,
|
||||
json_encoder: Type[JSONEncoder] = ...,
|
||||
**defaults: Any
|
||||
) -> None: ...
|
||||
# Silence type warnings, since this class overrides arguments and return types in an unsafe manner.
|
||||
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,
|
||||
path: str,
|
||||
data: Union[Dict[str, str], str] = ...,
|
||||
content_type: str = ...,
|
||||
follow: bool = ...,
|
||||
secure: bool = ...,
|
||||
**extra: Any
|
||||
) -> Any: ...
|
||||
def put(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
||||
def patch(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
||||
def delete(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
||||
def generic(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
data: Any = ...,
|
||||
content_type: Optional[str] = ...,
|
||||
secure: bool = ...,
|
||||
**extra: Any
|
||||
) -> Any: ...
|
||||
) -> HttpResponse: ... # type: ignore
|
||||
def put( # type: ignore
|
||||
self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
|
||||
) -> HttpResponse: ... # type: ignore
|
||||
def patch( # type: ignore
|
||||
self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
|
||||
) -> HttpResponse: ... # type: ignore
|
||||
def delete( # type: ignore
|
||||
self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
|
||||
) -> HttpResponse: ... # type: ignore
|
||||
def store_exc_info(self, **kwargs: Any) -> None: ...
|
||||
@property
|
||||
def session(self) -> SessionBase: ...
|
||||
|
||||
@@ -16,6 +16,7 @@ from typing import (
|
||||
Type,
|
||||
Union,
|
||||
ContextManager,
|
||||
TypeVar,
|
||||
)
|
||||
|
||||
from django.apps.registry import Apps
|
||||
@@ -29,6 +30,7 @@ from django.conf import LazySettings, Settings
|
||||
|
||||
_TestClass = Type[SimpleTestCase]
|
||||
_DecoratedTest = Union[Callable, _TestClass]
|
||||
_C = TypeVar("_C", bound=Callable) # Any callable
|
||||
|
||||
TZ_SUPPORT: bool = ...
|
||||
|
||||
@@ -56,7 +58,7 @@ class TestContextDecorator:
|
||||
def __enter__(self) -> Optional[Apps]: ...
|
||||
def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None: ...
|
||||
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: ...
|
||||
|
||||
class override_settings(TestContextDecorator):
|
||||
@@ -146,7 +148,7 @@ def get_unique_databases_and_mirrors() -> Tuple[Dict[_Signature, _TestDatabase],
|
||||
def teardown_databases(
|
||||
old_config: Iterable[Tuple[Any, str, bool]], verbosity: int, parallel: int = ..., keepdb: bool = ...
|
||||
) -> None: ...
|
||||
def require_jinja2(test_func: Callable) -> Callable: ...
|
||||
def require_jinja2(test_func: _C) -> _C: ...
|
||||
@contextmanager
|
||||
def register_lookup(
|
||||
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.views.generic.base import View
|
||||
|
||||
_T = TypeVar("_T", bound=Union[View, Callable]) # Any callable
|
||||
|
||||
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(middleware_class: type) -> Callable: ...
|
||||
def available_attrs(fn: Any): ...
|
||||
def available_attrs(fn: Callable): ...
|
||||
def make_middleware_decorator(middleware_class: Type[MiddlewareMixin]) -> Callable: ...
|
||||
|
||||
class classproperty:
|
||||
|
||||
@@ -64,7 +64,12 @@ IGNORED_ERRORS = {
|
||||
'Incompatible types in string interpolation',
|
||||
'"None" has no attribute',
|
||||
'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': [
|
||||
'"Article" has no attribute "non_field"',
|
||||
@@ -104,6 +109,7 @@ IGNORED_ERRORS = {
|
||||
],
|
||||
'builtin_server': [
|
||||
'"ServerHandler" has no attribute',
|
||||
'Incompatible types in assignment (expression has type "Tuple[BytesIO, BytesIO]"',
|
||||
],
|
||||
'bulk_create': [
|
||||
'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")'
|
||||
],
|
||||
'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': [
|
||||
'has incompatible type "str"'
|
||||
@@ -191,7 +198,7 @@ IGNORED_ERRORS = {
|
||||
],
|
||||
'from_db_value': [
|
||||
'"Cash" has no attribute',
|
||||
'Argument 1 to "__str__" of "Decimal"',
|
||||
'"__str__" of "Decimal"',
|
||||
],
|
||||
'get_object_or_404': [
|
||||
'Argument 1 to "get_object_or_404" has incompatible type "str"; '
|
||||
@@ -240,6 +247,7 @@ IGNORED_ERRORS = {
|
||||
],
|
||||
'middleware': [
|
||||
re.compile(r'"(HttpRequest|WSGIRequest)" has no attribute'),
|
||||
'Incompatible types in assignment (expression has type "HttpResponseBase", variable has type "HttpResponse")',
|
||||
],
|
||||
'many_to_many': [
|
||||
'(expression has type "List[Article]", variable has type "Article_RelatedManager2',
|
||||
@@ -276,6 +284,7 @@ IGNORED_ERRORS = {
|
||||
'"ImageFile" has no attribute "was_opened"',
|
||||
'Incompatible type for "size" of "FloatModel" (got "object", expected "Union[float, int, str, Combinable]")',
|
||||
'Incompatible type for "value" of "IntegerModel" (got "object", expected',
|
||||
'"Child" has no attribute "get_foo_display"',
|
||||
],
|
||||
'model_forms': [
|
||||
'"render" of "Widget"',
|
||||
@@ -376,6 +385,9 @@ IGNORED_ERRORS = {
|
||||
'settings_tests': [
|
||||
'Argument 1 to "Settings" has incompatible type "Optional[str]"; expected "str"'
|
||||
],
|
||||
'shortcuts': [
|
||||
'error: "Context" has no attribute "request"',
|
||||
],
|
||||
'signals': [
|
||||
'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"',
|
||||
],
|
||||
'syndication_tests': [
|
||||
'List or tuple expected as variable arguments'
|
||||
'Argument 1 to "add_domain" has incompatible type "*Tuple[object, ...]"',
|
||||
],
|
||||
'sessions_tests': [
|
||||
'Incompatible types in assignment (expression has type "None", variable has type "int")',
|
||||
@@ -428,6 +440,7 @@ IGNORED_ERRORS = {
|
||||
'test_client_regress': [
|
||||
'(expression has type "Dict[<nothing>, <nothing>]", variable has type "SessionBase")',
|
||||
'Unsupported left operand type for + ("None")',
|
||||
'Argument 1 to "len" has incompatible type "Context"; expected "Sized"',
|
||||
],
|
||||
'transactions': [
|
||||
'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]] = {
|
||||
'2.2': ('stable/2.2.x', '86befcc172c23170a720b3e0c06db51a99b3da59'),
|
||||
'3.0': ('stable/3.0.x', '6cb30414bc0f83b49afc4cae76d4af5656effe9a')
|
||||
'2.2': ('stable/2.2.x', '996be04c3ceb456754d9d527d4d708f30727f07e'),
|
||||
'3.0': ('stable/3.0.x', 'd9f1792c7649e9f946f4a3a35a76bddf5a412b8b')
|
||||
}
|
||||
PROJECT_DIRECTORY = Path(__file__).parent.parent
|
||||
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()
|
||||
|
||||
dependencies = [
|
||||
'mypy>=0.760,<0.770',
|
||||
'mypy>=0.770,<0.780',
|
||||
'typing-extensions',
|
||||
'django',
|
||||
]
|
||||
|
||||
setup(
|
||||
name="django-stubs",
|
||||
version="1.4.0",
|
||||
version="1.5.0",
|
||||
description='Mypy stubs for Django',
|
||||
long_description=readme,
|
||||
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