1 Commits

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

I have noticed this when I was writing a Jedi plugin that makes django-stubs work properly.
2020-06-11 23:21:10 +02:00
39 changed files with 107 additions and 375 deletions

View File

@@ -47,9 +47,8 @@ We rely on different `django` and `mypy` versions:
| django-stubs | mypy version | django version | python version | django-stubs | mypy version | django version | python version
| ------------ | ---- | ---- | ---- | | ------------ | ---- | ---- | ---- |
| 1.6.0 | 0.780 | 2.2.x \|\| 3.x | ^3.6 | 1.5.0 | 0.780 | 2.2.x \|\| 3.x | ^3.6
| 1.5.0 | 0.770 | 2.2.x \|\| 3.x | ^3.6 | 1.4.0 | 0.770 | 2.2.x \|\| 3.x | ^3.6
| 1.4.0 | 0.760 | 2.2.x \|\| 3.x | ^3.6
| 1.3.0 | 0.750 | 2.2.x \|\| 3.x | ^3.6 | 1.3.0 | 0.750 | 2.2.x \|\| 3.x | ^3.6
| 1.2.0 | 0.730 | 2.2.x | ^3.6 | 1.2.0 | 0.730 | 2.2.x | ^3.6
| 1.1.0 | 0.720 | 2.2.x | ^3.6 | 1.1.0 | 0.720 | 2.2.x | ^3.6
@@ -90,24 +89,19 @@ You can use strings instead: `'QuerySet[MyModel]'` and `'Manager[MyModel]'`, thi
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 create a HttpRequest that's guaranteed to have an authenticated user? ### How can I use HttpRequest with custom user model?
Django's built in `HttpRequest` has the attribute `user` that resolves to the type You can subclass standard request like so:
```python
Union[User, AnonymousUser]
```
where `User` is the user model specified by the `AUTH_USER_MODEL` setting.
If you want a `HttpRequest` that you can type-annotate with where you know that the user is authenticated you can subclass the normal `HttpRequest` class like so:
```python ```python
from django.http import HttpRequest from django.http import HttpRequest
from my_user_app.models import MyUser from my_user_app.models import MyUser
class AuthenticatedHttpRequest(HttpRequest): class MyRequest(HttpRequest):
user: MyUser user: MyUser
``` ```
And then use `AuthenticatedHttpRequest` instead of the standard `HttpRequest` for when you know that the user is authenticated. For example in views using the `@login_required` decorator. And then use `MyRequest` instead of standard `HttpRequest` inside your project.
## Related projects ## Related projects

View File

@@ -1,6 +1,6 @@
black black
pytest-mypy-plugins==1.3.0 pytest-mypy-plugins==1.3.0
psycopg2-binary 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

View File

@@ -135,7 +135,7 @@ class ModelAdmin(BaseModelAdmin):
delete_selected_confirmation_template: str = ... delete_selected_confirmation_template: str = ...
object_history_template: str = ... object_history_template: str = ...
popup_response_template: str = ... popup_response_template: str = ...
actions: Sequence[Union[Callable[[ModelAdmin, HttpRequest, QuerySet], None], str]] = ... actions: Sequence[Callable[[ModelAdmin, HttpRequest, QuerySet], None]] = ...
action_form: Any = ... action_form: Any = ...
actions_on_top: bool = ... actions_on_top: bool = ...
actions_on_bottom: bool = ... actions_on_bottom: bool = ...

View File

@@ -1,11 +1,7 @@
from typing import Callable, Optional, TypeVar, overload from typing import Callable, TypeVar, overload
_C = TypeVar("_C", bound=Callable) _C = TypeVar("_C", bound=Callable)
@overload @overload
def staff_member_required( def staff_member_required(view_func: _C = ..., redirect_field_name: str = ..., login_url: str = ...) -> _C: ...
view_func: _C = ..., redirect_field_name: Optional[str] = ..., login_url: str = ...
) -> _C: ...
@overload @overload
def staff_member_required( def staff_member_required(view_func: None = ..., redirect_field_name: str = ..., login_url: str = ...) -> Callable: ...
view_func: None = ..., redirect_field_name: Optional[str] = ..., login_url: str = ...
) -> Callable: ...

View File

@@ -2,7 +2,7 @@ from typing import Any, List, Optional, Type, Union
from django.contrib.auth.backends import ModelBackend from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.base_user import AbstractBaseUser from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AbstractUser, AnonymousUser
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
from django.db.models.base import Model from django.db.models.base import Model
from django.db.models.options import Options from django.db.models.options import Options
@@ -29,6 +29,6 @@ def logout(request: HttpRequest) -> None: ...
def get_user_model() -> Type[Model]: ... def get_user_model() -> Type[Model]: ...
def get_user(request: HttpRequest) -> Union[AbstractBaseUser, AnonymousUser]: ... def get_user(request: HttpRequest) -> Union[AbstractBaseUser, AnonymousUser]: ...
def get_permission_codename(action: str, opts: Options) -> str: ... def get_permission_codename(action: str, opts: Options) -> str: ...
def update_session_auth_hash(request: HttpRequest, user: AbstractBaseUser) -> None: ... def update_session_auth_hash(request: WSGIRequest, user: AbstractUser) -> None: ...
default_app_config: str default_app_config: str

View File

@@ -1,7 +1,7 @@
from typing import Any, Dict, Iterator, Optional from typing import Any, Dict, Iterator, Optional
from django.contrib.auth.base_user import AbstractBaseUser from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import User from django.contrib.auth.models import AbstractUser, User
from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
@@ -86,6 +86,6 @@ class AdminPasswordChangeForm(forms.Form):
password1: Any = ... password1: Any = ...
password2: Any = ... password2: Any = ...
user: User = ... user: User = ...
def __init__(self, user: AbstractBaseUser, *args: Any, **kwargs: Any) -> None: ... def __init__(self, user: AbstractUser, *args: Any, **kwargs: Any) -> None: ...
def clean_password2(self) -> str: ... def clean_password2(self) -> str: ...
def save(self, commit: bool = ...) -> AbstractBaseUser: ... def save(self, commit: bool = ...) -> AbstractUser: ...

View File

@@ -22,4 +22,4 @@ class Style:
def make_style(config_string: str = ...) -> Style: ... def make_style(config_string: str = ...) -> Style: ...
def no_style() -> Style: ... def no_style() -> Style: ...
def color_style(force_color: bool = ...) -> Style: ... def color_style() -> Style: ...

View File

@@ -29,8 +29,8 @@ class Paginator:
orphans: int = ..., orphans: int = ...,
allow_empty_first_page: bool = ..., allow_empty_first_page: bool = ...,
) -> None: ... ) -> None: ...
def validate_number(self, number: Optional[Union[int, float, str]]) -> int: ... def validate_number(self, number: Optional[Union[float, str]]) -> int: ...
def get_page(self, number: Optional[Union[int, float, str]]) -> Page: ... def get_page(self, number: Optional[int]) -> Page: ...
def page(self, number: Union[int, str]) -> Page: ... def page(self, number: Union[int, str]) -> Page: ...
@property @property
def count(self) -> int: ... def count(self) -> int: ...

View File

@@ -1,4 +1,4 @@
from typing import Any, Callable, Collection, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar, Union from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union, Collection, ClassVar
from django.core.checks.messages import CheckMessage from django.core.checks.messages import CheckMessage
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@@ -22,7 +22,7 @@ class Model(metaclass=ModelBase):
class Meta: ... class Meta: ...
_meta: Options[Any] _meta: Options[Any]
_default_manager: BaseManager[Model] _default_manager: BaseManager[Model]
objects: BaseManager[Any] objects: ClassVar[BaseManager[Any]]
pk: Any = ... pk: Any = ...
_state: ModelState _state: ModelState
def __init__(self: _Self, *args, **kwargs) -> None: ... def __init__(self: _Self, *args, **kwargs) -> None: ...
@@ -43,7 +43,7 @@ class Model(metaclass=ModelBase):
force_insert: bool = ..., force_insert: bool = ...,
force_update: bool = ..., force_update: bool = ...,
using: Optional[str] = ..., using: Optional[str] = ...,
update_fields: Optional[Iterable[str]] = ..., update_fields: Optional[Union[Sequence[str], str]] = ...,
) -> None: ... ) -> None: ...
def save_base( def save_base(
self, self,
@@ -51,7 +51,7 @@ class Model(metaclass=ModelBase):
force_insert: bool = ..., force_insert: bool = ...,
force_update: bool = ..., force_update: bool = ...,
using: Optional[str] = ..., using: Optional[str] = ...,
update_fields: Optional[Iterable[str]] = ..., update_fields: Optional[Union[Sequence[str], str]] = ...,
): ... ): ...
def refresh_from_db(self: _Self, using: Optional[str] = ..., fields: Optional[List[str]] = ...) -> None: ... def refresh_from_db(self: _Self, using: Optional[str] = ..., fields: Optional[List[str]] = ...) -> None: ...
def get_deferred_fields(self) -> Set[str]: ... def get_deferred_fields(self) -> Set[str]: ...

View File

@@ -1 +0,0 @@
LOOKUP_SEP: str = ...

View File

@@ -10,10 +10,6 @@ class ChoicesMeta(enum.EnumMeta):
class Choices(enum.Enum, metaclass=ChoicesMeta): class Choices(enum.Enum, metaclass=ChoicesMeta):
def __str__(self): ... def __str__(self): ...
@property
def label(self) -> str: ...
@property
def value(self) -> Any: ...
# fake # fake
class _IntegerChoicesMeta(ChoicesMeta): class _IntegerChoicesMeta(ChoicesMeta):
@@ -22,9 +18,7 @@ class _IntegerChoicesMeta(ChoicesMeta):
labels: List[str] = ... labels: List[str] = ...
values: List[int] = ... values: List[int] = ...
class IntegerChoices(int, Choices, metaclass=_IntegerChoicesMeta): class IntegerChoices(int, Choices, metaclass=_IntegerChoicesMeta): ...
@property
def value(self) -> int: ...
# fake # fake
class _TextChoicesMeta(ChoicesMeta): class _TextChoicesMeta(ChoicesMeta):
@@ -33,6 +27,4 @@ class _TextChoicesMeta(ChoicesMeta):
labels: List[str] = ... labels: List[str] = ...
values: List[str] = ... values: List[str] = ...
class TextChoices(str, Choices, metaclass=_TextChoicesMeta): class TextChoices(str, Choices, metaclass=_TextChoicesMeta): ...
@property
def value(self) -> str: ...

View File

@@ -1,5 +1,4 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from decimal import Decimal
from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union, Iterable from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union, Iterable
from django.db.models.lookups import Lookup from django.db.models.lookups import Lookup
@@ -16,8 +15,6 @@ class SQLiteNumericMixin:
_Self = TypeVar("_Self") _Self = TypeVar("_Self")
_Numeric = Union[float, Decimal]
class Combinable: class Combinable:
ADD: str = ... ADD: str = ...
SUB: str = ... SUB: str = ...
@@ -30,25 +27,25 @@ class Combinable:
BITLEFTSHIFT: str = ... BITLEFTSHIFT: str = ...
BITRIGHTSHIFT: str = ... BITRIGHTSHIFT: str = ...
def __neg__(self: _Self) -> _Self: ... def __neg__(self: _Self) -> _Self: ...
def __add__(self: _Self, other: Optional[Union[timedelta, Combinable, _Numeric, str]]) -> _Self: ... def __add__(self: _Self, other: Optional[Union[timedelta, Combinable, float, str]]) -> _Self: ...
def __sub__(self: _Self, other: Union[timedelta, Combinable, _Numeric]) -> _Self: ... def __sub__(self: _Self, other: Union[timedelta, Combinable, float]) -> _Self: ...
def __mul__(self: _Self, other: Union[timedelta, Combinable, _Numeric]) -> _Self: ... def __mul__(self: _Self, other: Union[timedelta, Combinable, float]) -> _Self: ...
def __truediv__(self: _Self, other: Union[Combinable, _Numeric]) -> _Self: ... def __truediv__(self: _Self, other: Union[Combinable, float]) -> _Self: ...
def __itruediv__(self: _Self, other: Union[Combinable, _Numeric]) -> _Self: ... def __itruediv__(self: _Self, other: Union[Combinable, float]) -> _Self: ...
def __mod__(self: _Self, other: Union[int, Combinable]) -> _Self: ... def __mod__(self: _Self, other: Union[int, Combinable]) -> _Self: ...
def __pow__(self: _Self, other: Union[_Numeric, Combinable]) -> _Self: ... def __pow__(self: _Self, other: Union[float, Combinable]) -> _Self: ...
def __and__(self: _Self, other: Combinable) -> _Self: ... def __and__(self: _Self, other: Combinable) -> _Self: ...
def bitand(self: _Self, other: int) -> _Self: ... def bitand(self: _Self, other: int) -> _Self: ...
def bitleftshift(self: _Self, other: int) -> _Self: ... def bitleftshift(self: _Self, other: int) -> _Self: ...
def bitrightshift(self: _Self, other: int) -> _Self: ... def bitrightshift(self: _Self, other: int) -> _Self: ...
def __or__(self: _Self, other: Combinable) -> _Self: ... def __or__(self: _Self, other: Combinable) -> _Self: ...
def bitor(self: _Self, other: int) -> _Self: ... def bitor(self: _Self, other: int) -> _Self: ...
def __radd__(self, other: Optional[Union[datetime, _Numeric, Combinable]]) -> Combinable: ... def __radd__(self, other: Optional[Union[datetime, float, Combinable]]) -> Combinable: ...
def __rsub__(self, other: Union[_Numeric, Combinable]) -> Combinable: ... def __rsub__(self, other: Union[float, Combinable]) -> Combinable: ...
def __rmul__(self, other: Union[_Numeric, Combinable]) -> Combinable: ... def __rmul__(self, other: Union[float, Combinable]) -> Combinable: ...
def __rtruediv__(self, other: Union[_Numeric, Combinable]) -> Combinable: ... def __rtruediv__(self, other: Union[float, Combinable]) -> Combinable: ...
def __rmod__(self, other: Union[int, Combinable]) -> Combinable: ... def __rmod__(self, other: Union[int, Combinable]) -> Combinable: ...
def __rpow__(self, other: Union[_Numeric, Combinable]) -> Combinable: ... def __rpow__(self, other: Union[float, Combinable]) -> Combinable: ...
def __rand__(self, other: Any) -> Combinable: ... def __rand__(self, other: Any) -> Combinable: ...
def __ror__(self, other: Any) -> Combinable: ... def __ror__(self, other: Any) -> Combinable: ...

View File

@@ -56,10 +56,6 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
remote_field: Field remote_field: Field
is_relation: bool is_relation: bool
related_model: Optional[Type[Model]] related_model: Optional[Type[Model]]
one_to_many: Optional[bool] = ...
one_to_one: Optional[bool] = ...
many_to_many: Optional[bool] = ...
many_to_one: Optional[bool] = ...
max_length: int max_length: int
model: Type[Model] model: Type[Model]
name: str name: str

View File

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

View File

@@ -96,7 +96,7 @@ class _BaseQuerySet(Generic[_T], Sized):
def union(self: _QS, *other_qs: Any, all: bool = ...) -> _QS: ... def union(self: _QS, *other_qs: Any, all: bool = ...) -> _QS: ...
def intersection(self: _QS, *other_qs: Any) -> _QS: ... def intersection(self: _QS, *other_qs: Any) -> _QS: ...
def difference(self: _QS, *other_qs: Any) -> _QS: ... def difference(self: _QS, *other_qs: Any) -> _QS: ...
def select_for_update(self: _QS, nowait: bool = ..., skip_locked: bool = ..., of: Sequence[str] = ...) -> _QS: ... def select_for_update(self: _QS, nowait: bool = ..., skip_locked: bool = ..., of: Tuple = ...) -> _QS: ...
def select_related(self: _QS, *fields: Any) -> _QS: ... def select_related(self: _QS, *fields: Any) -> _QS: ...
def prefetch_related(self: _QS, *lookups: Any) -> _QS: ... def prefetch_related(self: _QS, *lookups: Any) -> _QS: ...
# TODO: return type # TODO: return type

View File

@@ -14,7 +14,7 @@ class Field:
initial: Any initial: Any
label: Optional[str] label: Optional[str]
required: bool required: bool
widget: Union[Type[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

@@ -70,12 +70,7 @@ class BaseForm:
def visible_fields(self): ... def visible_fields(self): ...
def get_initial_for_field(self, field: Field, field_name: str) -> Any: ... def get_initial_for_field(self, field: Field, field_name: str) -> Any: ...
def _html_output( def _html_output(
self, self, normal_row: str, error_row: str, row_ender: str, help_text_html: str, errors_on_separate_row: bool,
normal_row: str,
error_row: str,
row_ender: str,
help_text_html: str,
errors_on_separate_row: bool,
) -> SafeText: ... ) -> SafeText: ...
class Form(BaseForm): class Form(BaseForm):

View File

@@ -83,13 +83,12 @@ class HttpResponse(HttpResponseBase):
context: Context context: Context
resolver_match: ResolverMatch resolver_match: ResolverMatch
def json(self) -> Any: ... def json(self) -> Any: ...
def getvalue(self) -> bytes: ...
class StreamingHttpResponse(HttpResponseBase): class StreamingHttpResponse(HttpResponseBase):
content: Any content: Any
streaming_content: Iterator[Any] streaming_content: Iterator[Any]
def __init__(self, streaming_content: Iterable[Any] = ..., *args: Any, **kwargs: Any) -> None: ... def __init__(self, streaming_content: Iterable[Any] = ..., *args: Any, **kwargs: Any) -> None: ...
def getvalue(self) -> bytes: ... def getvalue(self) -> Any: ...
class FileResponse(StreamingHttpResponse): class FileResponse(StreamingHttpResponse):
client: Client client: Client

View File

@@ -109,7 +109,7 @@ class Parser:
builtins: Optional[List[Library]] = ..., builtins: Optional[List[Library]] = ...,
origin: Optional[Origin] = ..., origin: Optional[Origin] = ...,
) -> None: ... ) -> None: ...
def parse(self, parse_until: Optional[Tuple[str, ...]] = ...) -> NodeList: ... def parse(self, parse_until: Optional[Tuple[str]] = ...) -> NodeList: ...
def skip_past(self, endtag: str) -> None: ... def skip_past(self, endtag: str) -> None: ...
def extend_nodelist(self, nodelist: NodeList, node: Node, token: Token) -> None: ... def extend_nodelist(self, nodelist: NodeList, node: Node, token: Token) -> None: ...
def error(self, token: Token, e: Union[Exception, str]) -> Exception: ... def error(self, token: Token, e: Union[Exception, str]) -> Exception: ...

View File

@@ -1,4 +1,4 @@
from typing import Any, List, Iterable, Optional, Dict from typing import Any, List, Optional, Dict
from django.template.base import Origin, Template from django.template.base import Origin, Template
from django.template.engine import Engine from django.template.engine import Engine
@@ -8,5 +8,5 @@ class Loader:
get_template_cache: Dict[str, Any] = ... get_template_cache: Dict[str, Any] = ...
def __init__(self, engine: Engine) -> None: ... def __init__(self, engine: Engine) -> None: ...
def get_template(self, template_name: str, skip: Optional[List[Origin]] = ...) -> Template: ... def get_template(self, template_name: str, skip: Optional[List[Origin]] = ...) -> Template: ...
def get_template_sources(self, template_name: str) -> Iterable[Origin]: ... def get_template_sources(self, template_name: str) -> None: ...
def reset(self) -> None: ... def reset(self) -> None: ...

View File

@@ -2,7 +2,7 @@ from io import BytesIO
from types import TracebackType 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.base_user import AbstractBaseUser 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.http.cookie import SimpleCookie from django.http.cookie import SimpleCookie
@@ -126,7 +126,7 @@ class Client(RequestFactory):
@property @property
def session(self) -> SessionBase: ... def session(self) -> SessionBase: ...
def login(self, **credentials: Any) -> bool: ... def login(self, **credentials: Any) -> bool: ...
def force_login(self, user: AbstractBaseUser, backend: Optional[str] = ...) -> None: ... def force_login(self, user: AbstractUser, backend: Optional[str] = ...) -> None: ...
def logout(self) -> None: ... def logout(self) -> None: ...
def conditional_content_removal(request: HttpRequest, response: HttpResponseBase) -> HttpResponse: ... def conditional_content_removal(request: HttpRequest, response: HttpResponseBase) -> HttpResponse: ...

View File

@@ -1,31 +1,8 @@
from typing import Any, List, Optional, Tuple, overload, Callable, Dict, Union from typing import Any, List, Optional, Tuple
from .resolvers import URLResolver, URLPattern from .resolvers import URLResolver
from ..conf.urls import IncludedURLConf
from ..http.response import HttpResponseBase
def include(arg: Any, namespace: Optional[str] = ...) -> Tuple[List[URLResolver], Optional[str], Optional[str]]: ... def include(arg: Any, namespace: Optional[str] = ...) -> Tuple[List[URLResolver], Optional[str], Optional[str]]: ...
# path() path: Any
@overload re_path: Any
def path(
route: str, view: Callable[..., HttpResponseBase], kwargs: Dict[str, Any] = ..., name: str = ...
) -> URLPattern: ...
@overload
def path(route: str, view: IncludedURLConf, kwargs: Dict[str, Any] = ..., name: str = ...) -> URLResolver: ...
@overload
def path(
route: str, view: List[Union[URLResolver, str]], kwargs: Dict[str, Any] = ..., name: str = ...
) -> URLResolver: ...
# re_path()
@overload
def re_path(
route: str, view: Callable[..., HttpResponseBase], kwargs: Dict[str, Any] = ..., name: str = ...
) -> URLPattern: ...
@overload
def re_path(route: str, view: IncludedURLConf, kwargs: Dict[str, Any] = ..., name: str = ...) -> URLResolver: ...
@overload
def re_path(
route: str, view: List[Union[URLResolver, str]], kwargs: Dict[str, Any] = ..., name: str = ...
) -> URLResolver: ...

View File

@@ -22,7 +22,6 @@ class ResolverMatch:
url_name: Optional[str] = ..., url_name: Optional[str] = ...,
app_names: Optional[List[Optional[str]]] = ..., app_names: Optional[List[Optional[str]]] = ...,
namespaces: Optional[List[Optional[str]]] = ..., namespaces: Optional[List[Optional[str]]] = ...,
route: Optional[str] = ...,
) -> None: ... ) -> None: ...
def __getitem__(self, index: int) -> Any: ... def __getitem__(self, index: int) -> Any: ...
# for tuple unpacking # for tuple unpacking

View File

@@ -56,8 +56,4 @@ class SimpleLazyObject(LazyObject):
def __copy__(self) -> List[int]: ... def __copy__(self) -> List[int]: ...
def __deepcopy__(self, memo: Dict[Any, Any]) -> List[int]: ... def __deepcopy__(self, memo: Dict[Any, Any]) -> List[int]: ...
_PartitionMember = TypeVar("_PartitionMember") def partition(predicate: Callable, values: List[Model]) -> Tuple[List[Model], List[Model]]: ...
def partition(
predicate: Callable, values: List[_PartitionMember]
) -> Tuple[List[_PartitionMember], List[_PartitionMember]]: ...

View File

@@ -57,7 +57,7 @@ class override(ContextDecorator):
def __enter__(self) -> None: ... def __enter__(self) -> None: ...
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 get_language() -> str: ... def get_language() -> Optional[str]: ...
def get_language_from_path(path: str) -> Optional[str]: ... def get_language_from_path(path: str) -> Optional[str]: ...
def get_language_bidi() -> bool: ... def get_language_bidi() -> bool: ...
def check_for_language(lang_code: Optional[str]) -> bool: ... def check_for_language(lang_code: Optional[str]) -> bool: ...

View File

@@ -1,35 +1,28 @@
from typing import Any, Callable, Dict, Optional, Sequence, Type, Union from typing import Any, Callable, Dict, Optional, Sequence, Type, Union
from django.forms.forms import BaseForm from django.forms.forms import BaseForm
from django.forms.models import BaseModelForm
from django.views.generic.base import ContextMixin, TemplateResponseMixin, View from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
from django.views.generic.detail import BaseDetailView, SingleObjectMixin, SingleObjectTemplateResponseMixin from django.views.generic.detail import BaseDetailView, SingleObjectMixin, SingleObjectTemplateResponseMixin
from typing_extensions import Literal from typing_extensions import Literal
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
class AbstractFormMixin(ContextMixin): class FormMixin(ContextMixin):
initial: Dict[str, Any] = ... initial: Dict[str, Any] = ...
form_class: Optional[Type[BaseForm]] = ... form_class: Optional[Type[BaseForm]] = ...
success_url: Optional[Union[str, Callable[..., Any]]] = ... success_url: Optional[Union[str, Callable[..., Any]]] = ...
prefix: Optional[str] = ... prefix: Optional[str] = ...
def get_initial(self) -> Dict[str, Any]: ... def get_initial(self) -> Dict[str, Any]: ...
def get_prefix(self) -> Optional[str]: ... def get_prefix(self) -> Optional[str]: ...
def get_form_kwargs(self) -> Dict[str, Any]: ...
def get_success_url(self) -> str: ...
class FormMixin(AbstractFormMixin):
def get_form_class(self) -> Type[BaseForm]: ... def get_form_class(self) -> Type[BaseForm]: ...
def get_form(self, form_class: Optional[Type[BaseForm]] = ...) -> BaseForm: ... def get_form(self, form_class: Optional[Type[BaseForm]] = ...) -> BaseForm: ...
def get_form_kwargs(self) -> Dict[str, Any]: ...
def get_success_url(self) -> str: ...
def form_valid(self, form: BaseForm) -> HttpResponse: ... def form_valid(self, form: BaseForm) -> HttpResponse: ...
def form_invalid(self, form: BaseForm) -> HttpResponse: ... def form_invalid(self, form: BaseForm) -> HttpResponse: ...
class ModelFormMixin(AbstractFormMixin, SingleObjectMixin): class ModelFormMixin(FormMixin, SingleObjectMixin):
fields: Optional[Union[Sequence[str], Literal["__all__"]]] = ... fields: Optional[Union[Sequence[str], Literal["__all__"]]] = ...
def get_form_class(self) -> Type[BaseModelForm]: ...
def get_form(self, form_class: Optional[Type[BaseModelForm]] = ...) -> BaseModelForm: ...
def form_valid(self, form: BaseModelForm) -> HttpResponse: ...
def form_invalid(self, form: BaseModelForm) -> HttpResponse: ...
class ProcessFormView(View): class ProcessFormView(View):
def get(self, request: HttpRequest, *args: str, **kwargs: Any) -> HttpResponse: ... def get(self, request: HttpRequest, *args: str, **kwargs: Any) -> HttpResponse: ...

View File

@@ -311,11 +311,7 @@ def add_new_sym_for_info(info: TypeInfo, *, name: str, sym_type: MypyType) -> No
def build_unannotated_method_args(method_node: FuncDef) -> Tuple[List[Argument], MypyType]: def build_unannotated_method_args(method_node: FuncDef) -> Tuple[List[Argument], MypyType]:
prepared_arguments = [] prepared_arguments = []
try: for argument in method_node.arguments[1:]:
arguments = method_node.arguments[1:]
except AttributeError:
arguments = []
for argument in arguments:
argument.type_annotation = AnyType(TypeOfAny.unannotated) argument.type_annotation = AnyType(TypeOfAny.unannotated)
prepared_arguments.append(argument) prepared_arguments.append(argument)
return_type = AnyType(TypeOfAny.unannotated) return_type = AnyType(TypeOfAny.unannotated)
@@ -347,7 +343,6 @@ def copy_method_to_another_class(ctx: ClassDefContext, self_type: Instance,
arguments = [] arguments = []
bound_return_type = semanal_api.anal_type(method_type.ret_type, bound_return_type = semanal_api.anal_type(method_type.ret_type,
allow_placeholder=True) allow_placeholder=True)
assert bound_return_type is not None assert bound_return_type is not None
if isinstance(bound_return_type, PlaceholderNode): if isinstance(bound_return_type, PlaceholderNode):
@@ -357,10 +352,6 @@ def copy_method_to_another_class(ctx: ClassDefContext, self_type: Instance,
method_type.arg_types[1:], method_type.arg_types[1:],
method_node.arguments[1:]): method_node.arguments[1:]):
bound_arg_type = semanal_api.anal_type(arg_type, allow_placeholder=True) bound_arg_type = semanal_api.anal_type(arg_type, allow_placeholder=True)
if bound_arg_type is None and not semanal_api.final_iteration:
semanal_api.defer()
return
assert bound_arg_type is not None assert bound_arg_type is not None
if isinstance(bound_arg_type, PlaceholderNode): if isinstance(bound_arg_type, PlaceholderNode):

View File

@@ -1,8 +1,9 @@
import configparser import configparser
from functools import partial from functools import partial
from typing import Callable, Dict, List, NoReturn, Optional, Tuple, cast from typing import Callable, Dict, List, Optional, Tuple
from django.db.models.fields.related import RelatedField from django.db.models.fields.related import RelatedField
from mypy.errors import Errors
from mypy.nodes import MypyFile, TypeInfo from mypy.nodes import MypyFile, TypeInfo
from mypy.options import Options from mypy.options import Options
from mypy.plugin import ( from mypy.plugin import (
@@ -51,40 +52,25 @@ def add_new_manager_base(ctx: ClassDefContext) -> None:
def extract_django_settings_module(config_file_path: Optional[str]) -> str: def extract_django_settings_module(config_file_path: Optional[str]) -> str:
errors = Errors()
def exit(error_type: int) -> NoReturn: if config_file_path is None:
"""Using mypy's argument parser, raise `SystemExit` to fail hard if validation fails. errors.report(0, None, "'django_settings_module' is not set: no mypy config file specified")
errors.raise_error()
Considering that the plugin's startup duration is around double as long as mypy's, this aims to
import and construct objects only when that's required - which happens once and terminates the
run. Considering that most of the runs are successful, there's no need for this to linger in the
global scope.
"""
from mypy.main import CapturableArgumentParser
usage = """(config)
...
[mypy.plugins.django_stubs]
django_settings_module: str (required)
...
""".replace("\n" + 8 * " ", "\n")
handler = CapturableArgumentParser(prog='(django-stubs) mypy', usage=usage)
messages = {1: 'mypy config file is not specified or found',
2: 'no section [mypy.plugins.django-stubs]',
3: 'the setting is not provided'}
handler.error("'django_settings_module' is not set: " + messages[error_type])
parser = configparser.ConfigParser() parser = configparser.ConfigParser()
try: parser.read(config_file_path) # type: ignore
parser.read_file(open(cast(str, config_file_path), 'r'), source=config_file_path)
except (IsADirectoryError, OSError):
exit(1)
section = 'mypy.plugins.django-stubs' if not parser.has_section('mypy.plugins.django-stubs'):
if not parser.has_section(section): errors.report(0, None, "'django_settings_module' is not set: no section [mypy.plugins.django-stubs]",
exit(2) file=config_file_path)
settings = parser.get(section, 'django_settings_module', fallback=None) or exit(3) errors.raise_error()
return cast(str, settings).strip('\'"') if not parser.has_option('mypy.plugins.django-stubs', 'django_settings_module'):
errors.report(0, None, "'django_settings_module' is not set: setting is not provided",
file=config_file_path)
errors.raise_error()
django_settings_module = parser.get('mypy.plugins.django-stubs', 'django_settings_module').strip('\'"')
return django_settings_module
class NewSemanalDjangoPlugin(Plugin): class NewSemanalDjangoPlugin(Plugin):

View File

@@ -8,22 +8,6 @@ from mypy_django_plugin.lib import helpers
def set_auth_user_model_as_type_for_request_user(ctx: AttributeContext, django_context: DjangoContext) -> MypyType: def set_auth_user_model_as_type_for_request_user(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
# Imported here because django isn't properly loaded yet when module is loaded
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import AnonymousUser
abstract_base_user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), AbstractBaseUser)
anonymous_user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), AnonymousUser)
# This shouldn't be able to happen, as we managed to import the models above.
assert abstract_base_user_info is not None
assert anonymous_user_info is not None
if ctx.default_attr_type != UnionType([Instance(abstract_base_user_info, []), Instance(anonymous_user_info, [])]):
# Type has been changed from the default in django-stubs.
# I.e. HttpRequest has been subclassed and user-type overridden, so let's leave it as is.
return ctx.default_attr_type
auth_user_model = django_context.settings.AUTH_USER_MODEL auth_user_model = django_context.settings.AUTH_USER_MODEL
user_cls = django_context.apps_registry.get_model(auth_user_model) user_cls = django_context.apps_registry.get_model(auth_user_model)
user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), user_cls) user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), user_cls)
@@ -31,4 +15,12 @@ def set_auth_user_model_as_type_for_request_user(ctx: AttributeContext, django_c
if user_info is None: if user_info is None:
return ctx.default_attr_type return ctx.default_attr_type
# Imported here because django isn't properly loaded yet when module is loaded
from django.contrib.auth.models import AnonymousUser
anonymous_user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), AnonymousUser)
if anonymous_user_info is None:
# This shouldn't be able to happen, as we managed to import the model above...
return Instance(user_info, [])
return UnionType([Instance(user_info, []), Instance(anonymous_user_info, [])]) return UnionType([Instance(user_info, []), Instance(anonymous_user_info, [])])

View File

@@ -1,7 +1,5 @@
[pytest] [pytest]
testpaths = testpaths = ./test-data
./test-plugin
./test-data
addopts = addopts =
--tb=native --tb=native
-s -s

View File

@@ -451,11 +451,9 @@ IGNORED_ERRORS = {
'urlpatterns': [ 'urlpatterns': [
'"object" not callable', '"object" not callable',
'"None" not callable', '"None" not callable',
'Argument 2 to "path" has incompatible type "Callable[[Any], None]"',
'Incompatible return value type (got "None", expected "HttpResponseBase")',
], ],
'urlpatterns_reverse': [ 'urlpatterns_reverse': [
'No overload variant of "path" matches argument types "str", "None"', 'List or tuple expected as variable arguments',
'No overload variant of "zip" matches argument types "Any", "object"', 'No overload variant of "zip" matches argument types "Any", "object"',
'Argument 1 to "get_callable" has incompatible type "int"' 'Argument 1 to "get_callable" has incompatible type "int"'
], ],

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', '8093aaa8ff9dd7386a069c6eb49fcc1c5980c033'), '2.2': ('stable/2.2.x', '996be04c3ceb456754d9d527d4d708f30727f07e'),
'3.0': ('stable/3.0.x', '44da7abda848f05caaed74f6a749038c87dedfda') '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

@@ -9,7 +9,7 @@ def find_stub_files(name: str) -> List[str]:
result = [] result = []
for root, dirs, files in os.walk(name): for root, dirs, files in os.walk(name):
for file in files: for file in files:
if file.endswith(".pyi"): if file.endswith('.pyi'):
if os.path.sep in root: if os.path.sep in root:
sub_root = root.split(os.path.sep, 1)[-1] sub_root = root.split(os.path.sep, 1)[-1]
file = os.path.join(sub_root, file) file = os.path.join(sub_root, file)
@@ -17,42 +17,38 @@ def find_stub_files(name: str) -> List[str]:
return result return result
with open("README.md", "r") as f: with open('README.md', 'r') as f:
readme = f.read() readme = f.read()
dependencies = [ dependencies = [
"mypy>=0.782,<0.790", 'mypy>=0.780,<0.790',
"typing-extensions", 'typing-extensions',
"django", 'django',
] ]
setup( setup(
name="django-stubs", name="django-stubs",
version="1.6.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',
license="MIT", license='MIT',
url="https://github.com/typeddjango/django-stubs", url="https://github.com/typeddjango/django-stubs",
author="Maksim Kurnikov", author="Maksim Kurnikov",
author_email="maxim.kurnikov@gmail.com", author_email="maxim.kurnikov@gmail.com",
py_modules=[], py_modules=[],
python_requires=">=3.6", python_requires='>=3.6',
install_requires=dependencies, install_requires=dependencies,
packages=["django-stubs", *find_packages(exclude=["scripts"])], packages=['django-stubs', *find_packages(exclude=['scripts'])],
package_data={"django-stubs": find_stub_files("django-stubs")}, package_data={'django-stubs': find_stub_files('django-stubs')},
classifiers=[ classifiers=[
"Development Status :: 3 - Alpha", 'Development Status :: 3 - Alpha',
"License :: OSI Approved :: MIT License", 'License :: OSI Approved :: MIT License',
"Programming Language :: Python :: 3.6", 'Programming Language :: Python :: 3.6',
"Programming Language :: Python :: 3.7", 'Programming Language :: Python :: 3.7',
"Programming Language :: Python :: 3.8", 'Programming Language :: Python :: 3.8'
"Framework :: Django",
"Framework :: Django :: 2.2",
"Framework :: Django :: 3.0",
"Typing :: Typed",
], ],
project_urls={ project_urls={
"Release notes": "https://github.com/typeddjango/django-stubs/releases", 'Release notes': 'https://github.com/typeddjango/django-stubs/releases',
}, },
) )

View File

@@ -65,15 +65,11 @@
delete_selected_confirmation_template = "template" delete_selected_confirmation_template = "template"
object_history_template = "template" object_history_template = "template"
popup_response_template = "template" popup_response_template = "template"
actions = (an_action, "a_method_action") actions = (an_action,)
actions_on_top = True actions_on_top = True
actions_on_bottom = False actions_on_bottom = False
actions_selection_counter = True actions_selection_counter = True
admin_site = AdminSite() admin_site = AdminSite()
def a_method_action(self, request, queryset):
pass
# This test is here to make sure we're not running into a mypy issue which is # This test is here to make sure we're not running into a mypy issue which is
# worked around using a somewhat complicated _ListOrTuple union type. Once the # worked around using a somewhat complicated _ListOrTuple union type. Once the
# issue is solved upstream this test should pass even with the workaround # issue is solved upstream this test should pass even with the workaround
@@ -127,4 +123,4 @@
pass pass
class A(admin.ModelAdmin): class A(admin.ModelAdmin):
actions = [an_action] # E: List item 0 has incompatible type "Callable[[None], None]"; expected "Union[Callable[[ModelAdmin, HttpRequest, QuerySet[Any]], None], str]" actions = [an_action] # E: List item 0 has incompatible type "Callable[[None], None]"; expected "Callable[[ModelAdmin, HttpRequest, QuerySet[Any]], None]"

View File

@@ -1,26 +0,0 @@
- case: field_to_many_and_to_one_attrs_bool_or_none_in_field_base_class
main: |
from django.db.models import Field
field: Field
my_bool: bool
my_bool = field.one_to_many
my_bool = field.one_to_one
my_bool = field.many_to_many
my_bool = field.many_to_one
# Narrowing the types should give us bool
assert field.one_to_many is not None
my_bool = field.one_to_many
assert field.one_to_one is not None
my_bool = field.one_to_one
assert field.many_to_many is not None
my_bool = field.many_to_many
assert field.many_to_one is not None
my_bool = field.many_to_one
out: |
main:6: error: Incompatible types in assignment (expression has type "Optional[bool]", variable has type "bool")
main:7: error: Incompatible types in assignment (expression has type "Optional[bool]", variable has type "bool")
main:8: error: Incompatible types in assignment (expression has type "Optional[bool]", variable has type "bool")
main:9: error: Incompatible types in assignment (expression has type "Optional[bool]", variable has type "bool")

View File

@@ -335,25 +335,3 @@
objects = MyManager() objects = MyManager()
class ChildUser(models.Model): class ChildUser(models.Model):
objects = MyManager() objects = MyManager()
- case: custom_manager_annotate_method_before_type_declaration
main: |
from myapp.models import ModelA, ModelB, ManagerA
reveal_type(ModelA.objects) # N: Revealed type is 'myapp.models.ModelA_ManagerA1[myapp.models.ModelA]'
reveal_type(ModelA.objects.do_something) # N: Revealed type is 'def (other_obj: myapp.models.ModelB) -> builtins.str'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class ManagerA(models.Manager):
def do_something(self, other_obj: "ModelB") -> str:
return 'test'
class ModelA(models.Model):
title = models.TextField()
objects = ManagerA()
class ModelB(models.Model):
movie = models.TextField()

View File

@@ -46,18 +46,6 @@
reveal_type(self.get_form(form_class)) # N: Revealed type is 'main.MyForm' reveal_type(self.get_form(form_class)) # N: Revealed type is 'main.MyForm'
reveal_type(self.get_form(MyForm2)) # N: Revealed type is 'main.MyForm2' reveal_type(self.get_form(MyForm2)) # N: Revealed type is 'main.MyForm2'
- case: updateview_form_valid_has_form_save
main: |
from django import forms
from django.views.generic.edit import UpdateView
class MyForm(forms.ModelForm):
pass
class MyView(UpdateView):
form_class = MyForm
def form_valid(self, form: forms.BaseModelForm):
reveal_type(form.save) # N: Revealed type is 'def (commit: builtins.bool =) -> Any'
- case: successmessagemixin_compatible_with_formmixin - case: successmessagemixin_compatible_with_formmixin
main: | main: |
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin

View File

@@ -27,28 +27,3 @@
reveal_type(request.user) # N: Revealed type is 'django.contrib.auth.models.User' reveal_type(request.user) # N: Revealed type is 'django.contrib.auth.models.User'
custom_settings: | custom_settings: |
INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth') INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth')
- case: subclass_request_not_changed_user_type
disable_cache: true
main: |
from django.http.request import HttpRequest
class MyRequest(HttpRequest):
foo: int # Just do something
request = MyRequest()
reveal_type(request.user) # N: Revealed type is 'Union[django.contrib.auth.models.User, django.contrib.auth.models.AnonymousUser]'
custom_settings: |
INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth')
- case: subclass_request_changed_user_type
disable_cache: true
main: |
from django.http.request import HttpRequest
from django.contrib.auth.models import User
class MyRequest(HttpRequest):
user: User # Override the type of user
request = MyRequest()
reveal_type(request.user) # N: Revealed type is 'django.contrib.auth.models.User'
custom_settings: |
INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth')

View File

@@ -1,73 +0,0 @@
import tempfile
import typing
import pytest
from mypy_django_plugin.main import extract_django_settings_module
TEMPLATE = """usage: (config)
...
[mypy.plugins.django_stubs]
django_settings_module: str (required)
...
(django-stubs) mypy: error: 'django_settings_module' is not set: {}
"""
@pytest.mark.parametrize(
'config_file_contents,message_part',
[
pytest.param(
None,
'mypy config file is not specified or found',
id='missing-file',
),
pytest.param(
['[not-really-django-stubs]'],
'no section [mypy.plugins.django-stubs]',
id='missing-section',
),
pytest.param(
['[mypy.plugins.django-stubs]',
'\tnot_django_not_settings_module = badbadmodule'],
'the setting is not provided',
id='missing-settings-module',
),
pytest.param(
['[mypy.plugins.django-stubs]'],
'the setting is not provided',
id='no-settings-given',
),
],
)
def test_misconfiguration_handling(capsys, config_file_contents, message_part):
# type: (typing.Any, typing.List[str], str) -> None
"""Invalid configuration raises `SystemExit` with a precise error message."""
with tempfile.NamedTemporaryFile(mode='w+') as config_file:
if not config_file_contents:
config_file.close()
else:
config_file.write('\n'.join(config_file_contents).expandtabs(4))
config_file.seek(0)
with pytest.raises(SystemExit, match='2'):
extract_django_settings_module(config_file.name)
error_message = TEMPLATE.format(message_part)
assert error_message == capsys.readouterr().err
def test_correct_configuration() -> None:
"""Django settings module gets extracted given valid configuration."""
config_file_contents = [
'[mypy.plugins.django-stubs]',
'\tsome_other_setting = setting',
'\tdjango_settings_module = my.module',
]
with tempfile.NamedTemporaryFile(mode='w+') as config_file:
config_file.write('\n'.join(config_file_contents).expandtabs(4))
config_file.seek(0)
extracted = extract_django_settings_module(config_file.name)
assert extracted == 'my.module'