25 Commits

Author SHA1 Message Date
Nikita Sobolev
3d2534ea8d Closes #392 2020-06-08 10:55:30 +03:00
Kacper
54f5f63e71 Issue 379 (#391)
* pytest-mypy-plugins package newer version

* Revert "pytest-mypy-plugins package newer version"

This reverts commit 871347a86577a5dad867bc751689bbc06d2bcae0.

* update tests ignores for django

* Revert "update tests ignores for django"

This reverts commit 93fc66e311af62cf8cf5b79a72ab723bf3cf060a.

* tests for python 3.8 WIP - initial commit

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-06 14:28:28 +03:00
Kacper
4c5723d368 WIP Issue 388 (#390)
* updated mypy dependency

* update readme

* readme update v2

* pytest-mypy-plugins newer version

* updated pytest_mypy_plugins name

* update ignored errors for typechecking django test suite

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-06 11:35:51 +03:00
Alexander Viklund
7e0e43135d BaseForm.prefix may be None (#389) 2020-06-04 21:24:42 +03:00
Kacper
e05b84e32d Issue 378 (#387)
* proper redirect return type annotations made with Literal

* Mapping instead of Dict type annotation for context in render() with test

* removed Union and Context

* typo

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>

Add __init__ to OrderedSet (#381)

Issue 382 (#384)

* WIP fix, pushed for testing

* added _ prefix for internal types

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>

Fix parameter types for assertJSONEqual/NotEqual (#385)

Add get_supported_language_variant (#386)

Issue 309 (#383)

* added tags for user models

* type test for HttpRequest.user

* test for User and AnonymousUser tags

* httrequest test fix

* checking python version fix for readibility

* Rewrite version check for readability

* Annotate is_authenticated/is_anonymous with Literal-type

* Add auth in INSTALLED_APPS in test

* Fix wrong type assertion in test

* Fix misconception of how branch-testing works

* Remove user from WSGIRequest

* Change HttpRequest-transformer to set user-type to include AnonymousUser

* Add check for anonymous_user_info=None to appease mypy

* Isort transformers/request

* Remove trailing whitespace

* Remove unused import

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>

* fix formatting and unused import

* reformatted again

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-03 23:46:30 +03:00
Alexander Viklund
71751d3795 Issue 309 (#383)
* added tags for user models

* type test for HttpRequest.user

* test for User and AnonymousUser tags

* httrequest test fix

* checking python version fix for readibility

* Rewrite version check for readability

* Annotate is_authenticated/is_anonymous with Literal-type

* Add auth in INSTALLED_APPS in test

* Fix wrong type assertion in test

* Fix misconception of how branch-testing works

* Remove user from WSGIRequest

* Change HttpRequest-transformer to set user-type to include AnonymousUser

* Add check for anonymous_user_info=None to appease mypy

* Isort transformers/request

* Remove trailing whitespace

* Remove unused import

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-03 20:29:19 +03:00
Alexander Viklund
25f92e8e56 Add get_supported_language_variant (#386) 2020-06-03 20:04:19 +03:00
Alexander Viklund
28d47c7e93 Fix parameter types for assertJSONEqual/NotEqual (#385) 2020-06-03 20:03:59 +03:00
Kacper
197cb4058e Issue 382 (#384)
* WIP fix, pushed for testing

* added _ prefix for internal types

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-03 12:58:03 +03:00
Alexander Viklund
dac2b31fb2 Add __init__ to OrderedSet (#381) 2020-06-02 13:56:11 +03:00
Kacper
8d2600136a Issue 355 (#376)
* Mapping instead of Dict type annotation for context in render() with test

* removed Union and Context

* typo

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-01 19:41:57 +03:00
Pavel Savchenko
570772f973 Revert "Allow template render to string helper functions to accept Context (#372)" (#377)
This reverts commit 64cbb0f70e.
2020-06-01 14:34:21 +03:00
Anton Agestam
d5c1bfb12a Increase accuracy of ModelAdmin types (#375)
* Increase accuracy of ModelAdmin types

* Add comment for regression test
2020-05-30 00:24:47 +03:00
Pavel Savchenko
64cbb0f70e Allow template render to string helper functions to accept Context (#372)
* Add failing test case for render_to_string

* Allow Context objects in template render functions
2020-05-23 13:47:39 +03:00
Ville Skyttä
6f5a39625e Add release notes project URL (#365)
Background info at https://github.com/pypa/warehouse/pull/7882
2020-05-05 18:38:11 +03:00
Anthony Ricaud
bf604a0398 Add BaseForm._html_output (#364) 2020-04-30 14:36:55 +03:00
Ashish Bansal (mrphantom)
92c8dfc93f Fix wrong BaseCache typings (#363)
django.core.cache.backends.base.BaseCache were invalid. This commit
fixes them in accordance with [0].

[0] 447980e72a/django/core/cache/backends/base.py (L108)
2020-04-24 12:44:55 +03:00
Jorgen Phillips
c10c55052c Add add_to_class method to Model (#361)
* Add add_to_class method to Model

* Fix @classmethod decorator
2020-04-24 01:11:16 +03:00
Sergey Tikhonov
96914e466b Annotate AdminForm.__init__ (#359) 2020-04-16 18:22:21 +03:00
Onyeka Aghanenu
90ed7f332d Fix Modelform auto_id to accept Boolean type. (#356) 2020-04-13 19:04:18 +03:00
Kacper
a801501151 stub for _build_app_dict (#351)
Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-04-04 20:38:42 +03:00
Sid Mitra
8ea59985df Add RESTRICT and RestrictedError to django.db.models.deletion (#345)
* Add RESTRICT and RestrictedError to django.db.models.deletion

* Add black fomatting changes
2020-04-03 17:02:34 +03:00
Ceesjan Luiten
2964ed53d7 Improve type of BaseForms.files (#350)
This is valid Django, which failed to pass with the old type-defintion:

    class MyForm(Form):
      myparam = MultiFileField(...)

      self.clean(self, *args, **kwargs):
        self.files.getlist('myparam', [])
2020-03-31 10:16:02 +03:00
Stevan Milic
1b9176f994 Add state attribute to Model (#347) (#348)
Add from_db method to Model

Co-authored-by: Stevan Milic <stevan.milic@tradecore.com>
2020-03-30 10:35:40 +03:00
Nikita Sobolev
54d0d018c6 Minor type improvements after linting (#343) 2020-03-19 18:04:51 +03:00
33 changed files with 380 additions and 91 deletions

View File

@@ -4,10 +4,19 @@ dist: xenial
sudo: required
jobs:
include:
- name: Run plugin test suite with python 3.8
python: 3.8
script: 'pytest'
- name: Run plugin test suite with python 3.7
python: 3.7
script: 'pytest'
- name: Typecheck Django 3.0 test suite with python 3.8
python: 3.8
script: |
python ./scripts/typecheck_tests.py --django_version=3.0
- name: Typecheck Django 3.0 test suite with python 3.7
python: 3.7
script: |

View File

@@ -47,7 +47,9 @@ We rely on different `django` and `mypy` versions:
| django-stubs | mypy version | django version | python version
| ------------ | ---- | ---- | ---- |
| 1.3.0 | 0.750 | 2.2.x | ^3.6
| 1.5.0 | 0.780 | 2.2.x \|\| 3.x | ^3.6
| 1.4.0 | 0.770 | 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.1.0 | 0.720 | 2.2.x | ^3.6
| 0.12.x | old semantic analyzer (<0.711), dmypy support | 2.1.x | ^3.6

View File

@@ -1,5 +1,5 @@
black
pytest-mypy-plugins==1.2.0
pytest-mypy-plugins==1.3.0
psycopg2
flake8==3.7.9
flake8-pyi==19.3.0

View File

@@ -1,7 +1,7 @@
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union, Iterable
from django.contrib.auth.forms import AdminPasswordChangeForm
from django.forms.boundfield import BoundField
from django.forms.forms import BaseForm
from django.forms.utils import ErrorDict
from django.forms.widgets import Media, Widget
from django.utils.safestring import SafeText
@@ -23,7 +23,7 @@ class AdminForm:
readonly_fields: Any = ...
def __init__(
self,
form: AdminPasswordChangeForm,
form: BaseForm,
fieldsets: List[Tuple[None, Dict[str, List[str]]]],
prepopulated_fields: Dict[Any, Any],
readonly_fields: Optional[Iterable[Any]] = ...,

View File

@@ -1,5 +1,9 @@
from collections import OrderedDict
from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union
from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union, Mapping, TypeVar
from django.forms.forms import BaseForm
from django.forms.formsets import BaseFormSet
from typing_extensions import Literal, TypedDict
from django.contrib.admin.filters import ListFilter
from django.contrib.admin.models import LogEntry
@@ -26,8 +30,10 @@ from django.db.models.fields import Field
IS_POPUP_VAR: str
TO_FIELD_VAR: str
HORIZONTAL: Any
VERTICAL: Any
HORIZONTAL: Literal[1] = ...
VERTICAL: Literal[2] = ...
_Direction = Union[Literal[1], Literal[2]]
def get_content_type_for_model(obj: Union[Type[Model], Model]) -> ContentType: ...
def get_ul_class(radio_style: int) -> str: ...
@@ -37,21 +43,35 @@ class IncorrectLookupParameters(Exception): ...
FORMFIELD_FOR_DBFIELD_DEFAULTS: Any
csrf_protect_m: Any
class _OptionalFieldOpts(TypedDict, total=False):
classes: Sequence[str]
description: str
class _FieldOpts(_OptionalFieldOpts, total=True):
fields: Sequence[Union[str, Sequence[str]]]
# Workaround for mypy issue, a Sequence type should be preferred here.
# https://github.com/python/mypy/issues/8921
# _FieldsetSpec = Sequence[Tuple[Optional[str], _FieldOpts]]
_T = TypeVar("_T")
_ListOrTuple = Union[Tuple[_T, ...], List[_T]]
_FieldsetSpec = _ListOrTuple[Tuple[Optional[str], _FieldOpts]]
class BaseModelAdmin:
autocomplete_fields: Any = ...
raw_id_fields: Any = ...
fields: Any = ...
exclude: Any = ...
fieldsets: Any = ...
form: Any = ...
filter_vertical: Any = ...
filter_horizontal: Any = ...
radio_fields: Any = ...
prepopulated_fields: Any = ...
formfield_overrides: Any = ...
readonly_fields: Any = ...
ordering: Any = ...
sortable_by: Any = ...
autocomplete_fields: Sequence[str] = ...
raw_id_fields: Sequence[str] = ...
fields: Sequence[Union[str, Sequence[str]]] = ...
exclude: Sequence[str] = ...
fieldsets: _FieldsetSpec = ...
form: Type[BaseForm] = ...
filter_vertical: Sequence[str] = ...
filter_horizontal: Sequence[str] = ...
radio_fields: Mapping[str, _Direction] = ...
prepopulated_fields: Mapping[str, Sequence[str]] = ...
formfield_overrides: Mapping[Type[Field], Mapping[str, Any]] = ...
readonly_fields: Sequence[Union[str, Callable[[Model], Any]]] = ...
ordering: Sequence[str] = ...
sortable_by: Sequence[str] = ...
view_on_site: bool = ...
show_full_result_count: bool = ...
checks_class: Any = ...
@@ -93,7 +113,7 @@ class BaseModelAdmin:
def has_module_permission(self, request: HttpRequest) -> bool: ...
class ModelAdmin(BaseModelAdmin):
list_display: Sequence[Union[str, Callable]] = ...
list_display: Sequence[Union[str, Callable[[Model], Any]]] = ...
list_display_links: Optional[Sequence[Union[str, Callable]]] = ...
list_filter: Sequence[Union[str, Type[ListFilter], Tuple[str, Type[ListFilter]]]] = ...
list_select_related: Union[bool, Sequence[str]] = ...
@@ -101,21 +121,21 @@ class ModelAdmin(BaseModelAdmin):
list_max_show_all: int = ...
list_editable: Sequence[str] = ...
search_fields: Sequence[str] = ...
date_hierarchy: Optional[Any] = ...
date_hierarchy: Optional[str] = ...
save_as: bool = ...
save_as_continue: bool = ...
save_on_top: bool = ...
paginator: Any = ...
paginator: Type = ...
preserve_filters: bool = ...
inlines: Sequence[Type[InlineModelAdmin]] = ...
add_form_template: Any = ...
change_form_template: Any = ...
change_list_template: Any = ...
delete_confirmation_template: Any = ...
delete_selected_confirmation_template: Any = ...
object_history_template: Any = ...
popup_response_template: Any = ...
actions: Any = ...
add_form_template: str = ...
change_form_template: str = ...
change_list_template: str = ...
delete_confirmation_template: str = ...
delete_selected_confirmation_template: str = ...
object_history_template: str = ...
popup_response_template: str = ...
actions: Sequence[Callable[[ModelAdmin, HttpRequest, QuerySet], None]] = ...
action_form: Any = ...
actions_on_top: bool = ...
actions_on_bottom: bool = ...
@@ -227,9 +247,9 @@ class ModelAdmin(BaseModelAdmin):
def history_view(self, request: HttpRequest, object_id: str, extra_context: None = ...) -> HttpResponse: ...
class InlineModelAdmin(BaseModelAdmin):
model: Any = ...
fk_name: Any = ...
formset: Any = ...
model: Type[Model] = ...
fk_name: str = ...
formset: BaseFormSet = ...
extra: int = ...
min_num: Optional[int] = ...
max_num: Optional[int] = ...
@@ -238,8 +258,8 @@ class InlineModelAdmin(BaseModelAdmin):
verbose_name_plural: Optional[str] = ...
can_delete: bool = ...
show_change_link: bool = ...
classes: Any = ...
admin_site: Any = ...
classes: Optional[Sequence[str]] = ...
admin_site: AdminSite = ...
parent_model: Any = ...
opts: Any = ...
has_registered_model: Any = ...

View File

@@ -63,6 +63,7 @@ class AdminSite:
def i18n_javascript(self, request: WSGIRequest, extra_context: Optional[Dict[Any, Any]] = ...) -> HttpResponse: ...
def logout(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> TemplateResponse: ...
def login(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> HttpResponse: ...
def _build_app_dict(self, request: WSGIRequest, label: Optional[str] = ...) -> Dict[str, Any]: ...
def get_app_list(self, request: WSGIRequest) -> List[Any]: ...
def index(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> TemplateResponse: ...
def app_index(

View File

@@ -1,9 +1,15 @@
import sys
from typing import Any, Optional, Tuple, List, overload, TypeVar
from django.db.models.base import Model
from django.db import models
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
_T = TypeVar("_T", bound=Model)
class BaseUserManager(models.Manager[_T]):
@@ -20,9 +26,9 @@ class AbstractBaseUser(models.Model):
def get_username(self) -> str: ...
def natural_key(self) -> Tuple[str]: ...
@property
def is_anonymous(self) -> bool: ...
def is_anonymous(self) -> Literal[False]: ...
@property
def is_authenticated(self) -> bool: ...
def is_authenticated(self) -> Literal[True]: ...
def set_password(self, raw_password: Optional[str]) -> None: ...
def check_password(self, raw_password: str) -> bool: ...
def set_unusable_password(self) -> None: ...

View File

@@ -1,3 +1,4 @@
import sys
from typing import Any, Collection, Optional, Set, Tuple, Type, TypeVar, Union
from django.contrib.auth.backends import ModelBackend
@@ -9,6 +10,11 @@ from django.db.models.manager import EmptyManager
from django.db import models
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
_AnyUser = Union[Model, "AnonymousUser"]
def update_last_login(sender: Type[AbstractBaseUser], user: AbstractBaseUser, **kwargs: Any) -> None: ...
@@ -105,7 +111,7 @@ class AnonymousUser:
def has_perms(self, perm_list: Collection[str], obj: Optional[_AnyUser] = ...) -> bool: ...
def has_module_perms(self, module: str) -> bool: ...
@property
def is_anonymous(self) -> bool: ...
def is_anonymous(self) -> Literal[True]: ...
@property
def is_authenticated(self) -> bool: ...
def is_authenticated(self) -> Literal[False]: ...
def get_username(self) -> str: ...

View File

@@ -1,5 +1,5 @@
from datetime import datetime
from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, List, Optional, Union, Protocol
from django.contrib.sites.models import Site
from django.contrib.sites.requests import RequestSite
@@ -13,10 +13,19 @@ class SitemapNotFound(Exception): ...
def ping_google(sitemap_url: Optional[str] = ..., ping_url: str = ...) -> None: ...
class _SupportsLen(Protocol):
def __len__(self) -> int: ...
class _SupportsCount(Protocol):
def count(self) -> int: ...
class _SupportsOrdered(Protocol):
ordered: bool = ...
class Sitemap:
limit: int = ...
protocol: Optional[str] = ...
def items(self) -> List[Any]: ...
def items(self) -> Union[_SupportsLen, _SupportsCount, _SupportsOrdered]: ...
def location(self, obj: Model) -> str: ...
@property
def paginator(self) -> Paginator: ...

View File

@@ -19,16 +19,16 @@ class BaseCache:
def __init__(self, params: Dict[str, Any]) -> None: ...
def get_backend_timeout(self, timeout: Any = ...) -> Optional[float]: ...
def make_key(self, key: Any, version: Optional[Any] = ...) -> str: ...
def add(self, key: Any, value: Any, timeout: Any = ..., version: Optional[Any] = ...) -> None: ...
def add(self, key: Any, value: Any, timeout: Any = ..., version: Optional[Any] = ...) -> bool: ...
def get(self, key: Any, default: Optional[Any] = ..., version: Optional[Any] = ...) -> Any: ...
def set(self, key: Any, value: Any, timeout: Any = ..., version: Optional[Any] = ...) -> None: ...
def touch(self, key: Any, timeout: Any = ..., version: Optional[Any] = ...) -> None: ...
def touch(self, key: Any, timeout: Any = ..., version: Optional[Any] = ...) -> bool: ...
def delete(self, key: Any, version: Optional[Any] = ...) -> None: ...
def get_many(self, keys: List[str], version: Optional[int] = ...) -> Dict[str, Union[int, str]]: ...
def get_or_set(
self, key: Any, default: Optional[Any], timeout: Any = ..., version: Optional[int] = ...
) -> Optional[Any]: ...
def has_key(self, key: Any, version: Optional[Any] = ...): ...
def has_key(self, key: Any, version: Optional[Any] = ...) -> bool: ...
def incr(self, key: str, delta: int = ..., version: Optional[int] = ...) -> int: ...
def decr(self, key: str, delta: int = ..., version: Optional[int] = ...) -> int: ...
def __contains__(self, key: str) -> bool: ...

View File

@@ -15,14 +15,14 @@ class SkipFile(UploadFileException): ...
class StopFutureHandlers(UploadFileException): ...
class FileUploadHandler:
chunk_size = ... # type: int
file_name = ... # type: Optional[str]
content_type = ... # type: Optional[str]
content_length = ... # type: Optional[int]
charset = ... # type: Optional[str]
content_type_extra = ... # type: Optional[Dict[str, str]]
request = ... # type: Optional[HttpRequest]
field_name = ... # type: str
chunk_size: int = ...
file_name: Optional[str] = ...
content_type: Optional[str] = ...
content_length: Optional[int] = ...
charset: Optional[str] = ...
content_type_extra: Optional[Dict[str, str]] = ...
request: Optional[HttpRequest] = ...
field_name: str = ...
def __init__(self, request: Optional[HttpRequest] = ...) -> None: ...
def handle_raw_input(
self,

View File

@@ -1,12 +1,10 @@
from io import BytesIO
from typing import Any, Callable, Dict, Optional, Union
from django.contrib.auth.models import AbstractUser
from django.contrib.sessions.backends.base import SessionBase
from django.http.response import HttpResponse
from django.core.handlers import base
from django.http import HttpRequest
from django.http.response import HttpResponse
_Stream = Union[BytesIO, str]
_WSGIEnviron = Dict[str, Any]
@@ -22,7 +20,6 @@ class LimitedStream:
class WSGIRequest(HttpRequest):
environ: _WSGIEnviron = ...
user: AbstractUser
session: SessionBase
encoding: Any = ...
def __init__(self, environ: _WSGIEnviron) -> None: ...

View File

@@ -8,13 +8,13 @@ class InvalidPage(Exception): ...
class PageNotAnInteger(InvalidPage): ...
class EmptyPage(InvalidPage): ...
class SupportsLen(Protocol):
class _SupportsLen(Protocol):
def __len__(self) -> int: ...
class SupportsCount(Protocol):
class _SupportsCount(Protocol):
def count(self) -> int: ...
class SupportsOrdered(Protocol):
class _SupportsOrdered(Protocol):
ordered: bool = ...
class Paginator:
@@ -24,7 +24,7 @@ class Paginator:
allow_empty_first_page: bool = ...
def __init__(
self,
object_list: Union[SupportsLen, SupportsCount, SupportsOrdered],
object_list: Union[_SupportsLen, _SupportsCount, _SupportsOrdered],
per_page: Union[int, str],
orphans: int = ...,
allow_empty_first_page: bool = ...,

View File

@@ -68,6 +68,9 @@ from .deletion import (
DO_NOTHING as DO_NOTHING,
PROTECT as PROTECT,
SET as SET,
RESTRICT as RESTRICT,
ProtectedError as ProtectedError,
RestrictedError as RestrictedError,
)
from .query import (

View File

@@ -7,6 +7,13 @@ from django.db.models.options import Options
_Self = TypeVar("_Self", bound="Model")
class ModelStateFieldsCacheDescriptor: ...
class ModelState:
db: Optional[str] = ...
adding: bool = ...
fields_cache: ModelStateFieldsCacheDescriptor = ...
class ModelBase(type): ...
class Model(metaclass=ModelBase):
@@ -17,7 +24,12 @@ class Model(metaclass=ModelBase):
_default_manager: BaseManager[Model]
objects: BaseManager[Any]
pk: Any = ...
_state: ModelState
def __init__(self: _Self, *args, **kwargs) -> None: ...
@classmethod
def add_to_class(cls, name: str, value: Any): ...
@classmethod
def from_db(cls, db: Optional[str], field_names: Collection[str], values: Collection[Any]) -> _Self: ...
def delete(self, using: Any = ..., keep_parents: bool = ...) -> Tuple[int, Dict[str, int]]: ...
def full_clean(self, exclude: Optional[Collection[str]] = ..., validate_unique: bool = ...) -> None: ...
def clean(self) -> None: ...
@@ -46,10 +58,3 @@ class Model(metaclass=ModelBase):
@classmethod
def check(cls, **kwargs: Any) -> List[CheckMessage]: ...
def __getstate__(self) -> dict: ...
class ModelStateFieldsCacheDescriptor: ...
class ModelState:
db: None = ...
adding: bool = ...
fields_cache: ModelStateFieldsCacheDescriptor = ...

View File

@@ -11,10 +11,12 @@ def SET_NULL(collector, field, sub_objs, using): ...
def SET_DEFAULT(collector, field, sub_objs, using): ...
def DO_NOTHING(collector, field, sub_objs, using): ...
def PROTECT(collector, field, sub_objs, using): ...
def RESTRICT(collector, field, sub_objs, using): ...
def SET(value: Any) -> Callable: ...
def get_candidate_relations_to_delete(opts: Options) -> Iterable[Field]: ...
class ProtectedError(IntegrityError): ...
class RestrictedError(IntegrityError): ...
class Collector:
def __init__(self, using: str) -> None: ...

View File

@@ -1,11 +1,13 @@
from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, Type, Union
from django.core.exceptions import ValidationError as ValidationError
from django.core.files import uploadedfile
from django.forms.boundfield import BoundField
from django.forms.fields import Field
from django.forms.renderers import BaseRenderer
from django.forms.utils import ErrorDict, ErrorList
from django.forms.widgets import Media, MediaDefiningClass
from django.utils.datastructures import MultiValueDict
from django.utils.safestring import SafeText
class DeclarativeFieldsMetaclass(MediaDefiningClass): ...
@@ -18,11 +20,11 @@ class BaseForm:
use_required_attribute: bool = ...
is_bound: bool = ...
data: Dict[str, Any] = ...
files: Optional[Dict[str, Any]] = ...
auto_id: str = ...
files: MultiValueDict[str, uploadedfile.UploadedFile] = ...
auto_id: Union[bool, str] = ...
initial: Dict[str, Any] = ...
error_class: Type[ErrorList] = ...
prefix: str = ...
prefix: Optional[str] = ...
label_suffix: str = ...
empty_permitted: bool = ...
fields: Dict[str, Any] = ...
@@ -67,6 +69,9 @@ class BaseForm:
def hidden_fields(self): ...
def visible_fields(self): ...
def get_initial_for_field(self, field: Field, field_name: str) -> Any: ...
def _html_output(
self, normal_row: str, error_row: str, row_ender: str, help_text_html: str, errors_on_separate_row: bool,
) -> SafeText: ...
class Form(BaseForm):
base_fields: Dict[str, Field]

View File

@@ -17,6 +17,7 @@ from typing import (
)
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import AnonymousUser
from django.contrib.sessions.backends.base import SessionBase
from django.contrib.sites.models import Site
from django.utils.datastructures import CaseInsensitiveMapping, ImmutableList, MultiValueDict
@@ -51,7 +52,7 @@ class HttpRequest(BytesIO):
resolver_match: ResolverMatch = ...
content_type: Optional[str] = ...
content_params: Optional[Dict[str, str]] = ...
user: AbstractBaseUser
user: Union[AbstractBaseUser, AnonymousUser]
site: Site
session: SessionBase
encoding: Optional[str] = ...

View File

@@ -1,4 +1,5 @@
from typing import Any, Callable, Dict, List, Optional, Protocol, Sequence, Type, TypeVar, Union
import sys
from typing import Any, Callable, List, Mapping, Optional, overload, Protocol, Sequence, Type, TypeVar, Union
from django.db.models.base import Model
from django.http.response import (
@@ -10,9 +11,14 @@ from django.http.response import (
from django.db.models import Manager, QuerySet
from django.http import HttpRequest
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
def render_to_response(
template_name: Union[str, Sequence[str]],
context: Optional[Dict[str, Any]] = ...,
context: Optional[Mapping[str, Any]] = ...,
content_type: Optional[str] = ...,
status: Optional[int] = ...,
using: Optional[str] = ...,
@@ -20,7 +26,7 @@ def render_to_response(
def render(
request: HttpRequest,
template_name: Union[str, Sequence[str]],
context: Optional[Dict[str, Any]] = ...,
context: Optional[Mapping[str, Any]] = ...,
content_type: Optional[str] = ...,
status: Optional[int] = ...,
using: Optional[str] = ...,
@@ -28,6 +34,15 @@ def render(
class SupportsGetAbsoluteUrl(Protocol): ...
@overload
def redirect(
to: Union[Callable, str, SupportsGetAbsoluteUrl], *args: Any, permanent: Literal[True], **kwargs: Any
) -> HttpResponsePermanentRedirect: ...
@overload
def redirect(
to: Union[Callable, str, SupportsGetAbsoluteUrl], *args: Any, permanent: Literal[False], **kwargs: Any
) -> HttpResponseRedirect: ...
@overload
def redirect(
to: Union[Callable, str, SupportsGetAbsoluteUrl], *args: Any, permanent: bool = ..., **kwargs: Any
) -> Union[HttpResponseRedirect, HttpResponsePermanentRedirect]: ...

View File

@@ -126,9 +126,17 @@ class SimpleTestCase(unittest.TestCase):
self, needle: str, haystack: SafeText, count: Optional[int] = ..., msg_prefix: str = ...
) -> None: ...
def assertJSONEqual(
self, raw: str, expected_data: Union[Dict[str, str], bool, str], msg: Optional[str] = ...
self,
raw: str,
expected_data: Union[Dict[str, Any], List[Any], str, int, float, bool, None],
msg: Optional[str] = ...,
) -> None: ...
def assertJSONNotEqual(
self,
raw: str,
expected_data: Union[Dict[str, Any], List[Any], str, int, float, bool, None],
msg: Optional[str] = ...,
) -> None: ...
def assertJSONNotEqual(self, raw: str, expected_data: str, msg: Optional[str] = ...) -> None: ...
def assertXMLEqual(self, xml1: str, xml2: str, msg: Optional[str] = ...) -> None: ...
def assertXMLNotEqual(self, xml1: str, xml2: str, msg: Optional[str] = ...) -> None: ...

View File

@@ -22,6 +22,7 @@ _V = TypeVar("_V")
class OrderedSet(MutableSet[_K]):
dict: Dict[_K, None] = ...
def __init__(self, iterable: Optional[Iterable[_K]] = ...) -> None: ...
def __contains__(self, item: object) -> bool: ...
def __iter__(self) -> Iterator[_K]: ...
def __len__(self) -> int: ...

View File

@@ -13,6 +13,6 @@ NOCOLOR_PALETTE: str
DARK_PALETTE: str
LIGHT_PALETTE: str
PALETTES: Any
DEFAULT_PALETTE = DARK_PALETTE
DEFAULT_PALETTE: str = ...
def parse_color_setting(config_string: str) -> Optional[Dict[str, Dict[str, Union[Tuple[str], str]]]]: ...

View File

@@ -66,6 +66,7 @@ def to_locale(language: str) -> str: ...
def get_language_from_request(request: WSGIRequest, check_path: bool = ...) -> str: ...
def templatize(src: str, **kwargs: Any) -> str: ...
def deactivate_all() -> None: ...
def get_supported_language_variant(lang_code: str, strict: bool = ...) -> str: ...
def get_language_info(lang_code: str) -> Any: ...
from . import trans_real as trans_real

View File

@@ -10,3 +10,5 @@ select = F401, Y
max_line_length = 120
per-file-ignores =
*__init__.pyi: F401
base_user.pyi: Y003
models.pyi: Y003

View File

@@ -1,6 +1,7 @@
from mypy.plugin import AttributeContext
from mypy.types import Instance
from mypy.types import Type as MypyType
from mypy.types import UnionType
from mypy_django_plugin.django.context import DjangoContext
from mypy_django_plugin.lib import helpers
@@ -8,9 +9,18 @@ from mypy_django_plugin.lib import helpers
def set_auth_user_model_as_type_for_request_user(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
auth_user_model = django_context.settings.AUTH_USER_MODEL
model_cls = django_context.apps_registry.get_model(auth_user_model)
model_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), model_cls)
if model_info is None:
user_cls = django_context.apps_registry.get_model(auth_user_model)
user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), user_cls)
if user_info is None:
return ctx.default_attr_type
return Instance(model_info, [])
# 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, [])])

View File

@@ -185,6 +185,8 @@ IGNORED_ERRORS = {
],
'files': [
'Incompatible types in assignment (expression has type "IOBase", variable has type "File")',
'Argument 1 to "TextIOWrapper" has incompatible type "File"; expected "BinaryIO"',
'Incompatible types in assignment (expression has type "BinaryIO", variable has type "File")',
],
'filtered_relation': [
'has no attribute "name"',
@@ -378,6 +380,7 @@ IGNORED_ERRORS = {
'responses': [
'Argument 1 to "TextIOWrapper" has incompatible type "HttpResponse"; expected "IO[bytes]"',
'"FileLike" has no attribute "closed"',
'Argument 1 to "TextIOWrapper" has incompatible type "HttpResponse"; expected "BinaryIO"',
],
'reverse_lookup': [
"Cannot resolve keyword 'choice' into field"

View File

@@ -1,5 +1,5 @@
from pytest_mypy.collect import File
from pytest_mypy.item import YamlTestItem
from pytest_mypy_plugins.collect import File
from pytest_mypy_plugins.item import YamlTestItem
def django_plugin_hook(test_item: YamlTestItem) -> None:

View File

@@ -21,7 +21,7 @@ with open('README.md', 'r') as f:
readme = f.read()
dependencies = [
'mypy>=0.770,<0.780',
'mypy>=0.780,<0.790',
'typing-extensions',
'django',
]
@@ -45,6 +45,10 @@ setup(
'Development Status :: 3 - Alpha',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7'
]
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8'
],
project_urls={
'Release notes': 'https://github.com/typeddjango/django-stubs/releases',
},
)

View File

@@ -0,0 +1,126 @@
# "Happy path" test for model admin, trying to cover as many valid
# configurations as possible.
- case: test_full_admin
main: |
from django.contrib import admin
from django.forms import Form, Textarea
from django.db import models
from django.core.paginator import Paginator
from django.contrib.admin.sites import AdminSite
from django.db.models.options import Options
from django.http.request import HttpRequest
from django.db.models.query import QuerySet
def an_action(modeladmin: admin.ModelAdmin, request: HttpRequest, queryset: QuerySet) -> None:
pass
class A(admin.ModelAdmin):
# BaseModelAdmin
autocomplete_fields = ("strs",)
raw_id_fields = ["strs"]
fields = (
"a field",
["a", "list of", "fields"],
)
exclude = ("a", "b")
fieldsets = [
(None, {"fields": ["a", "b"]}),
("group", {"fields": ("c",), "classes": ("a",), "description": "foo"}),
]
form = Form
filter_vertical = ("fields",)
filter_horizontal = ("plenty", "of", "fields")
radio_fields = {
"some_field": admin.VERTICAL,
"another_field": admin.HORIZONTAL,
}
prepopulated_fields = {"slug": ("title",)}
formfield_overrides = {models.TextField: {"widget": Textarea}}
readonly_fields = ("date_modified",)
ordering = ("-pk", "date_modified")
sortable_by = ["pk"]
view_on_site = True
show_full_result_count = False
# ModelAdmin
list_display = ("pk",)
list_display_links = ("str",)
list_filter = ("str", admin.SimpleListFilter, ("str", admin.SimpleListFilter))
list_select_related = True
list_per_page = 1
list_max_show_all = 2
list_editable = ("a", "b")
search_fields = ("c", "d")
date_hirearchy = "f"
save_as = False
save_as_continue = True
save_on_top = False
paginator = Paginator
presserve_filters = False
inlines = (admin.TabularInline, admin.StackedInline)
add_form_template = "template"
change_form_template = "template"
change_list_template = "template"
delete_confirmation_template = "template"
delete_selected_confirmation_template = "template"
object_history_template = "template"
popup_response_template = "template"
actions = (an_action,)
actions_on_top = True
actions_on_bottom = False
actions_selection_counter = True
admin_site = AdminSite()
# 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
# issue is solved upstream this test should pass even with the workaround
# replaced by a simpler Sequence type.
# https://github.com/python/mypy/issues/8921
- case: test_fieldset_workaround_regression
main: |
from django.contrib import admin
class A(admin.ModelAdmin):
fieldsets = (
(None, {
'fields': ('name',),
}),
)
- case: errors_on_omitting_fields_from_fieldset_opts
main: |
from django.contrib import admin
class A(admin.ModelAdmin):
fieldsets = [ # type: ignore
(None, {}), # E: Key 'fields' missing for TypedDict "_FieldOpts"
]
- case: errors_on_invalid_radio_fields
main: |
from django.contrib import admin
class A(admin.ModelAdmin):
radio_fields = {"some_field": 0} # E: Dict entry 0 has incompatible type "str": "Literal[0]"; expected "str": "Union[Literal[1], Literal[2]]"
class B(admin.ModelAdmin):
radio_fields = {1: admin.VERTICAL} # E: Dict entry 0 has incompatible type "int": "Literal[2]"; expected "str": "Union[Literal[1], Literal[2]]"
- case: errors_for_invalid_formfield_overrides
main: |
from django.contrib import admin
from django.forms import Textarea
class A(admin.ModelAdmin):
formfield_overrides = {
"not a field": { # E: Dict entry 0 has incompatible type "str": "Dict[str, Any]"; expected "Type[Field[Any, Any]]": "Mapping[str, Any]"
"widget": Textarea
}
}
- case: errors_for_invalid_action_signature
main: |
from django.contrib import admin
from django.http.request import HttpRequest
from django.db.models.query import QuerySet
def an_action(modeladmin: None) -> None:
pass
class A(admin.ModelAdmin):
actions = [an_action] # E: List item 0 has incompatible type "Callable[[None], None]"; expected "Callable[[ModelAdmin, HttpRequest, QuerySet[Any]], None]"

View File

@@ -10,6 +10,12 @@
reveal_type(User().is_active) # N: Revealed type is 'builtins.bool*'
reveal_type(User().date_joined) # N: Revealed type is 'datetime.datetime*'
reveal_type(User().last_login) # N: Revealed type is 'Union[datetime.datetime, None]'
reveal_type(User().is_authenticated) # N: Revealed type is 'Literal[True]'
reveal_type(User().is_anonymous) # N: Revealed type is 'Literal[False]'
from django.contrib.auth.models import AnonymousUser
reveal_type(AnonymousUser().is_authenticated) # N: Revealed type is 'Literal[False]'
reveal_type(AnonymousUser().is_anonymous) # N: Revealed type is 'Literal[True]'
from django.contrib.auth.models import Permission
reveal_type(Permission().name) # N: Revealed type is 'builtins.str*'

View File

@@ -0,0 +1,14 @@
- case: state_attribute_has_a_type_of_model_state
main: |
from myapp.models import MyUser
user = MyUser(pk=1)
reveal_type(user._state) # N: Revealed type is 'django.db.models.base.ModelState'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyUser(models.Model):
pass

View File

@@ -2,11 +2,11 @@
disable_cache: true
main: |
from django.http.request import HttpRequest
reveal_type(HttpRequest().user) # N: Revealed type is 'myapp.models.MyUser'
reveal_type(HttpRequest().user) # N: Revealed type is 'Union[myapp.models.MyUser, django.contrib.auth.models.AnonymousUser]'
# check that other fields work ok
reveal_type(HttpRequest().method) # N: Revealed type is 'Union[builtins.str, None]'
custom_settings: |
INSTALLED_APPS = ('django.contrib.contenttypes', 'myapp')
INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth', 'myapp')
AUTH_USER_MODEL='myapp.MyUser'
files:
- path: myapp/__init__.py
@@ -14,4 +14,16 @@
content: |
from django.db import models
class MyUser(models.Model):
pass
pass
- case: request_object_user_can_be_descriminated
disable_cache: true
main: |
from django.http.request import HttpRequest
request = HttpRequest()
reveal_type(request.user) # N: Revealed type is 'Union[django.contrib.auth.models.User, django.contrib.auth.models.AnonymousUser]'
if not request.user.is_anonymous:
reveal_type(request.user) # N: Revealed type is 'django.contrib.auth.models.User'
if request.user.is_authenticated:
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

@@ -36,3 +36,24 @@
from django.db import models
class MyUser(models.Model):
pass
- case: check_render_function_arguments_annotations
main: |
from typing import Any
from typing_extensions import TypedDict
from django.shortcuts import render
from django.http.request import HttpRequest
TestContext = TypedDict("TestContext", {"user": Any})
test_context: TestContext = {"user": "test"}
reveal_type(test_context) # N: Revealed type is 'TypedDict('main.TestContext', {'user': Any})'
reveal_type(render(HttpRequest(), '', test_context)) # N: Revealed type is 'django.http.response.HttpResponse'
- case: check_redirect_return_annotation
main: |
from django.shortcuts import redirect
reveal_type(redirect(to = '', permanent = True)) # N: Revealed type is 'django.http.response.HttpResponsePermanentRedirect'
reveal_type(redirect(to = '', permanent = False)) # N: Revealed type is 'django.http.response.HttpResponseRedirect'
var = True
reveal_type(redirect(to = '', permanent = var)) # N: Revealed type is 'Union[django.http.response.HttpResponseRedirect, django.http.response.HttpResponsePermanentRedirect]'