18 Commits

Author SHA1 Message Date
Maxim Kurnikov
c3d76f9a1e bump version 2019-03-10 20:03:00 +03:00
Maxim Kurnikov
fde071b883 fix classonlymethod, replace with six from typeshed 2019-03-10 19:56:54 +03:00
Seth Yastrov
324b961d74 Support returning the correct values for the different QuerySet methods when using .values() and .values_list(). (#33)
* Support returning the correct values for the different QuerySet methods when using .values() and .values_list().

* Fix slicing on QuerySet. Fix django queries test, and remove some ignored errors that are no longer needed.

* Remove accidental change in RawQuerySet.

* Readded some still-necessary ignores to aggregation django test.

* Add more tests of first/last/earliest/last/__getitem__, per mkurnikov's comments.

- Fix .iterator()

* Re-add Iterator as base-class of QuerySet.

* Make QuerySet a Collection.

* - Fix return type for QuerySet.select_for_update().
- Use correct return type for QuerySet.dates() / QuerySet.datetimes().
- Use correct type params in return type for QuerySet.__and__ / QuerySet.__or__
- Re-add Sized as base class for QuerySet.
- Add test of .all() for all _Row types.
- Add test of .get() for all _Row types.
- Remove some redundant QuerySet method tests.

* Automatically fill in second type parameter for QuerySet.

... if second parameter is omitted.
2019-03-10 12:13:50 +03:00
Seth Yastrov
86c63d790b Fix type errors on other models' managers when using objects = models.Manager() in Model. (#34)
* Fix bug where models with a class variable using a manager defined would interfere with other managers.

- Fill in the type argument for that particular instance of the manager, rather than modifying the bases of the Manager type.
- Instantiate a new Instance from determine_proper_manager_type so The code doesn't crash under mypy-mypyc.

* Use helpers.reparametrize_instance per review comment.

* Updated ignored errors in Django test for get_objects_or_404.

- For some reason, `Manager[nothing]` is now removed from expected types.
  However, I think this makes sense anyway, as Manager is a subclass of QuerySet.
2019-03-08 12:30:38 +03:00
Matt Basta
050c1b8887 Add gzip_page decorator (#41)
* Add gzip_page decorator

* Update to preserve Callable
2019-03-06 21:16:24 +03:00
Maxim Kurnikov
8978ad471f bump version 2019-03-06 12:05:24 +03:00
Richard Eames
f7dfbefbd6 Make CharField(blank=True) not be considered nullable (#39)
* Make CharField(blank=True) not be considered nullable

The documentation on [blank](https://docs.djangoproject.com/en/2.1/ref/models/fields/#blank) says that it "will allow the entry of an empty value", which for a string is just a 0-length string. This patch allows `CharField(blank=True,...)` to no longer be considered `Optional`.

closes #38

* fixed tests for `CharField(blank=True)`

* allow blank CharField to be nullable in the constructor, but the underlying type
is str (unless `null=True`)
2019-03-06 01:37:44 +03:00
Maxim Kurnikov
627daa55f5 fix extension of django.views __init__ file 2019-03-06 01:28:42 +03:00
Maxim Kurnikov
194489ee8d bump version 2019-03-05 20:21:43 +03:00
Maxim Kurnikov
1d2c7fb805 Remove _Val alias for MultiValueDict so that generic evaluate (#36)
* remove _Val alias for MultiValueDict so that generic evaluate

* fix multivaluedict init argument
2019-03-05 20:16:24 +03:00
Maxim Kurnikov
18c908bf98 set plugin_generated on new symbol nodes 2019-03-05 20:15:46 +03:00
Maxim Kurnikov
e0e8814804 Revert "dont convert to optional, if anytype"
This reverts commit 53f5d2214b.
2019-03-05 19:11:02 +03:00
Maxim Kurnikov
53f5d2214b dont convert to optional, if anytype 2019-03-05 18:43:10 +03:00
Maxim Kurnikov
9e4ed70fc5 Disable note: messages (#35)
* add global note: ignore
2019-03-01 05:15:05 +03:00
Maxim Kurnikov
18445f686f set fallback= for ini parser 2019-03-01 02:25:15 +03:00
Maxim Kurnikov
c962b8ac68 attempt to add flake8 and isort 2019-03-01 02:07:53 +03:00
Maxim Kurnikov
70c3126348 add plugin testing for python3.6 2019-02-27 18:12:29 +03:00
Maxim Kurnikov
af8ecc5520 remove django from dependencies, it's not required for static analysis 2019-02-27 18:11:54 +03:00
28 changed files with 529 additions and 295 deletions

View File

@@ -4,20 +4,34 @@ dist: xenial
sudo: required
jobs:
include:
- name: Typecheck Django test suite
python: 3.7
script: 'python ./scripts/typecheck_tests.py'
- name: Run plugin test suite with python 3.7
python: 3.7
script: |
set -e
pytest
- name: Run plugin test suite with python 3.6
python: 3.6
script: |
set -e
pytest
- name: Typecheck Django test suite
python: 3.7
script: 'python ./scripts/typecheck_tests.py'
- name: Lint with black
python: 3.7
script: 'black --check --line-length=120 django-stubs/'
- name: Lint plugin code with flake8
python: 3.7
script: 'flake8'
- name: Lint plugin code with isort
python: 3.7
script: 'isort --check'
before_install: |
# Upgrade pip, setuptools, and wheel
pip install -U pip setuptools wheel

View File

@@ -1,3 +1,5 @@
black
pytest-mypy-plugins
flake8
isort==4.3.4
-e .

View File

@@ -5,7 +5,7 @@ from django.db.models.query import QuerySet
_T = TypeVar("_T", bound=Model, covariant=True)
class BaseManager(QuerySet[_T]):
class BaseManager(QuerySet[_T, _T]):
creation_counter: int = ...
auto_created: bool = ...
use_in_migrations: bool = ...
@@ -21,7 +21,7 @@ class BaseManager(QuerySet[_T]):
def _get_queryset_methods(cls, queryset_class: type) -> Dict[str, Any]: ...
def contribute_to_class(self, model: Type[Model], name: str) -> None: ...
def db_manager(self, using: Optional[str] = ..., hints: Optional[Dict[str, Model]] = ...) -> Manager: ...
def get_queryset(self) -> QuerySet[_T]: ...
def get_queryset(self) -> QuerySet[_T, _T]: ...
class Manager(BaseManager[_T]): ...

View File

@@ -1,3 +1,4 @@
import datetime
from typing import (
Any,
Dict,
@@ -13,6 +14,9 @@ from typing import (
TypeVar,
Union,
overload,
Generic,
NamedTuple,
Collection,
)
from django.db.models.base import Model
@@ -46,7 +50,7 @@ class FlatValuesListIterable(BaseIterable):
_T = TypeVar("_T", bound=models.Model, covariant=True)
class QuerySet(Iterable[_T], Sized):
class QuerySet(Generic[_T, _Row], Collection[_Row], Sized):
query: Query
def __init__(
self,
@@ -58,32 +62,33 @@ class QuerySet(Iterable[_T], Sized):
@classmethod
def as_manager(cls) -> Manager[Any]: ...
def __len__(self) -> int: ...
def __iter__(self) -> Iterator[_T]: ...
def __iter__(self) -> Iterator[_Row]: ...
def __contains__(self, x: object) -> bool: ...
@overload
def __getitem__(self, i: int) -> _Row: ...
@overload
def __getitem__(self, s: slice) -> QuerySet[_T, _Row]: ...
def __bool__(self) -> bool: ...
def __class_getitem__(cls, item: Type[_T]):
pass
def __getstate__(self) -> Dict[str, Any]: ...
@overload
def __getitem__(self, k: int) -> _T: ...
@overload
def __getitem__(self, k: str) -> Any: ...
@overload
def __getitem__(self, k: slice) -> QuerySet[_T]: ...
def __and__(self, other: QuerySet) -> QuerySet: ...
def __or__(self, other: QuerySet) -> QuerySet: ...
def iterator(self, chunk_size: int = ...) -> Iterator[_T]: ...
# __and__ and __or__ ignore the other QuerySet's _Row type parameter because they use the same row type as the self QuerySet.
# Technically, the other QuerySet must be of the same type _T, but _T is covariant
def __and__(self, other: QuerySet[_T, Any]) -> QuerySet[_T, _Row]: ...
def __or__(self, other: QuerySet[_T, Any]) -> QuerySet[_T, _Row]: ...
def iterator(self, chunk_size: int = ...) -> Iterator[_Row]: ...
def aggregate(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: ...
def get(self, *args: Any, **kwargs: Any) -> _T: ...
def get(self, *args: Any, **kwargs: Any) -> _Row: ...
def create(self, **kwargs: Any) -> _T: ...
def bulk_create(self, objs: Iterable[Model], batch_size: Optional[int] = ...) -> List[_T]: ...
def get_or_create(self, defaults: Optional[MutableMapping[str, Any]] = ..., **kwargs: Any) -> Tuple[_T, bool]: ...
def update_or_create(
self, defaults: Optional[MutableMapping[str, Any]] = ..., **kwargs: Any
) -> Tuple[_T, bool]: ...
def earliest(self, *fields: Any, field_name: Optional[Any] = ...) -> _T: ...
def latest(self, *fields: Any, field_name: Optional[Any] = ...) -> _T: ...
def first(self) -> Optional[_T]: ...
def last(self) -> Optional[_T]: ...
def earliest(self, *fields: Any, field_name: Optional[Any] = ...) -> _Row: ...
def latest(self, *fields: Any, field_name: Optional[Any] = ...) -> _Row: ...
def first(self) -> Optional[_Row]: ...
def last(self) -> Optional[_Row]: ...
def in_bulk(self, id_list: Iterable[Any] = ..., *, field_name: str = ...) -> Dict[Any, _T]: ...
def delete(self) -> Tuple[int, Dict[str, int]]: ...
def update(self, **kwargs: Any) -> int: ...
@@ -93,31 +98,38 @@ class QuerySet(Iterable[_T], Sized):
def raw(
self, raw_query: str, params: Any = ..., translations: Optional[Dict[str, str]] = ..., using: None = ...
) -> RawQuerySet: ...
def values(self, *fields: Union[str, Combinable], **expressions: Any) -> QuerySet: ...
def values_list(self, *fields: Union[str, Combinable], flat: bool = ..., named: bool = ...) -> QuerySet: ...
# @overload
# def values_list(self, *fields: Union[str, Combinable], named: Literal[True]) -> NamedValuesListIterable: ...
# @overload
# def values_list(self, *fields: Union[str, Combinable], flat: Literal[True]) -> FlatValuesListIterable: ...
# @overload
# def values_list(self, *fields: Union[str, Combinable]) -> ValuesListIterable: ...
def dates(self, field_name: str, kind: str, order: str = ...) -> QuerySet: ...
def datetimes(self, field_name: str, kind: str, order: str = ..., tzinfo: None = ...) -> QuerySet: ...
def none(self) -> QuerySet[_T]: ...
def all(self) -> QuerySet[_T]: ...
def filter(self, *args: Any, **kwargs: Any) -> QuerySet[_T]: ...
def exclude(self, *args: Any, **kwargs: Any) -> QuerySet[_T]: ...
def complex_filter(self, filter_obj: Any) -> QuerySet[_T]: ...
def values(self, *fields: Union[str, Combinable], **expressions: Any) -> QuerySet[_T, Dict[str, Any]]: ...
@overload
def values_list(
self, *fields: Union[str, Combinable], flat: Literal[False] = ..., named: Literal[True]
) -> QuerySet[_T, NamedTuple]: ...
@overload
def values_list(
self, *fields: Union[str, Combinable], flat: Literal[True], named: Literal[False] = ...
) -> QuerySet[_T, Any]: ...
@overload
def values_list(
self, *fields: Union[str, Combinable], flat: Literal[False] = ..., named: Literal[False] = ...
) -> QuerySet[_T, Tuple]: ...
def dates(self, field_name: str, kind: str, order: str = ...) -> QuerySet[_T, datetime.date]: ...
def datetimes(
self, field_name: str, kind: str, order: str = ..., tzinfo: None = ...
) -> QuerySet[_T, datetime.datetime]: ...
def none(self) -> QuerySet[_T, _Row]: ...
def all(self) -> QuerySet[_T, _Row]: ...
def filter(self, *args: Any, **kwargs: Any) -> QuerySet[_T, _Row]: ...
def exclude(self, *args: Any, **kwargs: Any) -> QuerySet[_T, _Row]: ...
def complex_filter(self, filter_obj: Any) -> QuerySet[_T, _Row]: ...
def count(self) -> int: ...
def union(self, *other_qs: Any, all: bool = ...) -> QuerySet[_T]: ...
def intersection(self, *other_qs: Any) -> QuerySet[_T]: ...
def difference(self, *other_qs: Any) -> QuerySet[_T]: ...
def select_for_update(self, nowait: bool = ..., skip_locked: bool = ..., of: Tuple = ...) -> QuerySet: ...
def select_related(self, *fields: Any) -> QuerySet[_T]: ...
def prefetch_related(self, *lookups: Any) -> QuerySet[_T]: ...
def annotate(self, *args: Any, **kwargs: Any) -> QuerySet[_T]: ...
def order_by(self, *field_names: Any) -> QuerySet[_T]: ...
def distinct(self, *field_names: Any) -> QuerySet[_T]: ...
def union(self, *other_qs: Any, all: bool = ...) -> QuerySet[_T, _Row]: ...
def intersection(self, *other_qs: Any) -> QuerySet[_T, _Row]: ...
def difference(self, *other_qs: Any) -> QuerySet[_T, _Row]: ...
def select_for_update(self, nowait: bool = ..., skip_locked: bool = ..., of: Tuple = ...) -> QuerySet[_T, _Row]: ...
def select_related(self, *fields: Any) -> QuerySet[_T, _Row]: ...
def prefetch_related(self, *lookups: Any) -> QuerySet[_T, _Row]: ...
def annotate(self, *args: Any, **kwargs: Any) -> QuerySet[_T, _Row]: ...
def order_by(self, *field_names: Any) -> QuerySet[_T, _Row]: ...
def distinct(self, *field_names: Any) -> QuerySet[_T, _Row]: ...
def extra(
self,
select: Optional[Dict[str, Any]] = ...,
@@ -126,11 +138,11 @@ class QuerySet(Iterable[_T], Sized):
tables: Optional[List[str]] = ...,
order_by: Optional[Sequence[str]] = ...,
select_params: Optional[Sequence[Any]] = ...,
) -> QuerySet[_T]: ...
def reverse(self) -> QuerySet[_T]: ...
def defer(self, *fields: Any) -> QuerySet[_T]: ...
def only(self, *fields: Any) -> QuerySet[_T]: ...
def using(self, alias: Optional[str]) -> QuerySet[_T]: ...
) -> QuerySet[_T, _Row]: ...
def reverse(self) -> QuerySet[_T, _Row]: ...
def defer(self, *fields: Any) -> QuerySet[_T, _Row]: ...
def only(self, *fields: Any) -> QuerySet[_T, _Row]: ...
def using(self, alias: Optional[str]) -> QuerySet[_T, _Row]: ...
@property
def ordered(self) -> bool: ...
@property
@@ -159,7 +171,7 @@ class RawQuerySet(Iterable[_T], Sized):
@overload
def __getitem__(self, k: str) -> Any: ...
@overload
def __getitem__(self, k: slice) -> QuerySet[_T]: ...
def __getitem__(self, k: slice) -> RawQuerySet[_T]: ...
@property
def columns(self) -> List[str]: ...
@property

View File

@@ -31,6 +31,6 @@ def redirect(
_T = TypeVar("_T", bound=Model)
def get_object_or_404(klass: Union[Type[_T], Manager[_T], QuerySet[_T]], *args: Any, **kwargs: Any) -> _T: ...
def get_list_or_404(klass: Union[Type[_T], Manager[_T], QuerySet[_T]], *args: Any, **kwargs: Any) -> List[_T]: ...
def get_object_or_404(klass: Union[Type[_T], Manager[_T], QuerySet[_T, _T]], *args: Any, **kwargs: Any) -> _T: ...
def get_list_or_404(klass: Union[Type[_T], Manager[_T], QuerySet[_T, _T]], *args: Any, **kwargs: Any) -> List[_T]: ...
def resolve_url(to: Union[Callable, Model, str], *args: Any, **kwargs: Any) -> str: ...

View File

@@ -12,8 +12,11 @@ from typing import (
Union,
overload,
Iterator,
Optional,
)
from typing_extensions import Literal
_K = TypeVar("_K")
_V = TypeVar("_V")
@@ -27,24 +30,22 @@ class OrderedSet(MutableSet[_K]):
class MultiValueDictKeyError(KeyError): ...
_Val = Union[_V, List[_V]]
class MultiValueDict(MutableMapping[_K, _V]):
@overload
def __init__(self, key_to_list_mapping: Iterable[Tuple[_K, _Val]] = ...) -> None: ...
def __init__(self, key_to_list_mapping: Mapping[_K, Optional[List[_V]]] = ...) -> None: ...
@overload
def __init__(self, key_to_list_mapping: Mapping[_K, _Val] = ...) -> None: ...
def __init__(self, key_to_list_mapping: Iterable[Tuple[_K, List[_V]]] = ...) -> None: ...
def getlist(self, key: _K, default: List[_V] = None) -> List[_V]: ...
def setlist(self, key: _K, list_: List[_V]) -> None: ...
def setlistdefault(self, key: _K, default_list: List[_V] = None) -> List[_V]: ...
def appendlist(self, key: _K, value: _V) -> None: ...
def lists(self) -> Iterable[Tuple[_K, List[_V]]]: ...
def dict(self) -> Dict[_K, _Val]: ...
def dict(self) -> Dict[_K, Union[_V, List[_V]]]: ...
def copy(self) -> MultiValueDict[_K, _V]: ...
# These overrides are needed to convince mypy that this isn't an abstract class
def __delitem__(self, item: _K) -> None: ...
def __getitem__(self, item: _K) -> _Val: ... # type: ignore
def __setitem__(self, k: _K, v: _Val) -> None: ...
def __getitem__(self, item: _K) -> Union[_V, Literal[[]]]: ... # type: ignore
def __setitem__(self, k: _K, v: Union[_V, List[_V]]) -> None: ...
def __len__(self) -> int: ...
def __iter__(self) -> Iterator[_K]: ...

View File

@@ -1,18 +1,10 @@
from typing import Any, Callable, Optional, Set, Tuple, Type, Union
from django.contrib.auth.mixins import AccessMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.middleware.cache import CacheMiddleware
from django.test.testcases import LiveServerTestCase
from django.utils.deprecation import MiddlewareMixin
from django.views.generic.base import TemplateResponseMixin, View
class classonlymethod(classmethod):
def __get__(
self,
instance: Optional[View],
cls: Type[Union[AccessMixin, SuccessMessageMixin, TemplateResponseMixin, View]] = ...,
) -> Callable: ...
class classonlymethod(classmethod): ...
def method_decorator(
decorator: Union[Callable, Set[Callable], Tuple[Callable, Callable]], name: str = ...

View File

@@ -1,144 +1,106 @@
import types
from typing import Any, Optional
from __future__ import print_function
PY2: Any
PY3: Any
PY34: Any
string_types: Any
integer_types: Any
class_types: Any
import types
import typing
import unittest
from typing import (
Any,
AnyStr,
Callable,
Dict,
ItemsView,
Iterable,
KeysView,
Mapping,
NoReturn,
Optional,
Pattern,
Text,
Tuple,
Type,
TypeVar,
Union,
ValuesView,
overload,
)
# Exports
_T = TypeVar("_T")
_K = TypeVar("_K")
_V = TypeVar("_V")
# TODO make constant, then move this stub to 2and3
# https://github.com/python/typeshed/issues/17
PY2 = False
PY3 = True
PY34 = ... # type: bool
string_types = (str,)
integer_types = (int,)
class_types = (type,)
text_type = str
binary_type = bytes
MAXSIZE: Any
text_type = unicode
binary_type = str
class X:
def __len__(self): ...
MAXSIZE = ... # type: int
class _LazyDescr:
name: Any = ...
def __init__(self, name: Any) -> None: ...
def __get__(self, obj: Any, tp: Any): ...
# def add_move
# def remove_move
class MovedModule(_LazyDescr):
mod: Any = ...
def __init__(self, name: Any, old: Any, new: Optional[Any] = ...) -> None: ...
def __getattr__(self, attr: Any): ...
class _LazyModule(types.ModuleType):
__doc__: Any = ...
def __init__(self, name: Any) -> None: ...
def __dir__(self): ...
class MovedAttribute(_LazyDescr):
mod: Any = ...
attr: Any = ...
def __init__(
self, name: Any, old_mod: Any, new_mod: Any, old_attr: Optional[Any] = ..., new_attr: Optional[Any] = ...
) -> None: ...
class _SixMetaPathImporter:
name: Any = ...
known_modules: Any = ...
def __init__(self, six_module_name: Any) -> None: ...
def find_module(self, fullname: Any, path: Optional[Any] = ...): ...
def load_module(self, fullname: Any): ...
def is_package(self, fullname: Any): ...
def get_code(self, fullname: Any): ...
get_source: Any = ...
class _MovedItems(_LazyModule):
__path__: Any = ...
moves: Any
class Module_six_moves_urllib_parse(_LazyModule): ...
class Module_six_moves_urllib_error(_LazyModule): ...
class Module_six_moves_urllib_request(_LazyModule): ...
class Module_six_moves_urllib_response(_LazyModule): ...
class Module_six_moves_urllib_robotparser(_LazyModule): ...
class Module_six_moves_urllib(types.ModuleType):
__path__: Any = ...
parse: Any = ...
error: Any = ...
request: Any = ...
response: Any = ...
robotparser: Any = ...
def __dir__(self): ...
def add_move(move: Any) -> None: ...
def remove_move(name: Any) -> None: ...
advance_iterator = next
next = advance_iterator
callable = callable
def get_unbound_function(unbound: Any): ...
create_bound_method: Any
def create_unbound_method(func: Any, cls: Any): ...
def callable(obj: object) -> bool: ...
def get_unbound_function(unbound: types.FunctionType) -> types.FunctionType: ...
def create_bound_method(func: types.FunctionType, obj: object) -> types.MethodType: ...
def create_unbound_method(func: types.FunctionType, cls: type) -> types.FunctionType: ...
Iterator = object
class Iterator:
def next(self): ...
def get_method_function(meth: types.MethodType) -> types.FunctionType: ...
def get_method_self(meth: types.MethodType) -> Optional[object]: ...
def get_function_closure(fun: types.FunctionType) -> Optional[Tuple[types._Cell, ...]]: ...
def get_function_code(fun: types.FunctionType) -> types.CodeType: ...
def get_function_defaults(fun: types.FunctionType) -> Optional[Tuple[Any, ...]]: ...
def get_function_globals(fun: types.FunctionType) -> Dict[str, Any]: ...
def iterkeys(d: Mapping[_K, _V]) -> typing.Iterator[_K]: ...
def itervalues(d: Mapping[_K, _V]) -> typing.Iterator[_V]: ...
def iteritems(d: Mapping[_K, _V]) -> typing.Iterator[Tuple[_K, _V]]: ...
callable = callable
get_method_function: Any
get_method_self: Any
get_function_closure: Any
get_function_code: Any
get_function_defaults: Any
get_function_globals: Any
# def iterlists
def iterkeys(d: Any, **kw: Any): ...
def itervalues(d: Any, **kw: Any): ...
def iteritems(d: Any, **kw: Any): ...
def iterlists(d: Any, **kw: Any): ...
viewkeys: Any
viewvalues: Any
viewitems: Any
def b(s: Any): ...
def u(s: Any): ...
def viewkeys(d: Mapping[_K, _V]) -> KeysView[_K]: ...
def viewvalues(d: Mapping[_K, _V]) -> ValuesView[_V]: ...
def viewitems(d: Mapping[_K, _V]) -> ItemsView[_K, _V]: ...
def b(s: str) -> binary_type: ...
def u(s: str) -> text_type: ...
unichr = chr
int2byte: Any
byte2int: Any
indexbytes: Any
iterbytes = iter
StringIO: Any
BytesIO: Any
unichr = unichr
int2byte = chr
def assertCountEqual(self, *args: Any, **kwargs: Any): ...
def assertRaisesRegex(self, *args: Any, **kwargs: Any): ...
def assertRegex(self, *args: Any, **kwargs: Any): ...
def int2byte(i: int) -> bytes: ...
def byte2int(bs: binary_type) -> int: ...
def indexbytes(buf: binary_type, i: int) -> int: ...
def iterbytes(buf: binary_type) -> typing.Iterator[int]: ...
def assertCountEqual(
self: unittest.TestCase, first: Iterable[_T], second: Iterable[_T], msg: Optional[str] = ...
) -> None: ...
@overload
def assertRaisesRegex(self: unittest.TestCase, msg: Optional[str] = ...) -> Any: ...
@overload
def assertRaisesRegex(self: unittest.TestCase, callable_obj: Callable[..., Any], *args: Any, **kwargs: Any) -> Any: ...
def assertRegex(
self: unittest.TestCase, text: AnyStr, expected_regex: Union[AnyStr, Pattern[AnyStr]], msg: Optional[str] = ...
) -> None: ...
exec_: Any
exec_ = exec
def reraise(tp: Any, value: Any, tb: Optional[Any] = ...) -> None: ...
def raise_from(value: Any, from_value: Any) -> None: ...
def reraise(
tp: Optional[Type[BaseException]], value: Optional[BaseException], tb: Optional[types.TracebackType] = ...
) -> NoReturn: ...
def raise_from(value: Union[BaseException, Type[BaseException]], from_value: Optional[BaseException]) -> NoReturn: ...
print_: Any
_print = print_
print_ = print
def wraps(wrapped: Any, assigned: Any = ..., updated: Any = ...): ...
wraps: Any
def with_metaclass(meta: Any, *bases: Any): ...
def add_metaclass(metaclass: Any): ...
def python_2_unicode_compatible(klass: Any): ...
__path__: Any
__package__ = __name__
memoryview = memoryview
buffer_types: Any
memoryview = memoryview
memoryview = buffer
def with_metaclass(meta: type, *bases: type) -> type: ...
def add_metaclass(metaclass: type) -> Callable[[_T], _T]: ...
def ensure_binary(s: Union[bytes, Text], encoding: str = ..., errors: str = ...) -> bytes: ...
def ensure_str(s: Union[bytes, Text], encoding: str = ..., errors: str = ...) -> str: ...
def ensure_text(s: Union[bytes, Text], encoding: str = ..., errors: str = ...) -> Text: ...
def python_2_unicode_compatible(klass: _T) -> _T: ...

View File

@@ -0,0 +1,5 @@
from typing import Callable, TypeVar
_C = TypeVar("_C", bound=Callable)
def gzip_page(view_func: _C) -> _C: ...

View File

@@ -1,5 +1,5 @@
from configparser import ConfigParser
from typing import List, Optional
from typing import Optional
from dataclasses import dataclass
@@ -20,6 +20,7 @@ class Config:
fallback=None)
if django_settings:
django_settings = django_settings.strip()
return Config(django_settings_module=django_settings,
ignore_missing_settings=ini_config.get('mypy_django_plugin', 'ignore_missing_settings',
fallback=False))
ignore_missing_settings=bool(ini_config.get('mypy_django_plugin', 'ignore_missing_settings',
fallback=False)))

View File

@@ -2,10 +2,13 @@ import typing
from typing import Dict, Optional
from mypy.checker import TypeChecker
from mypy.nodes import AssignmentStmt, ClassDef, Expression, ImportedName, Lvalue, MypyFile, NameExpr, SymbolNode, \
TypeInfo
from mypy.nodes import (
AssignmentStmt, ClassDef, Expression, ImportedName, Lvalue, MypyFile, NameExpr, SymbolNode, TypeInfo,
)
from mypy.plugin import FunctionContext, MethodContext
from mypy.types import AnyType, Instance, NoneTyp, Type, TypeOfAny, TypeVarType, UnionType
from mypy.types import (
AnyType, Instance, NoneTyp, Type, TypeOfAny, TypeVarType, UnionType,
)
MODEL_CLASS_FULLNAME = 'django.db.models.base.Model'
FIELD_FULLNAME = 'django.db.models.fields.Field'

View File

@@ -1,20 +1,31 @@
from functools import partial
import os
from typing import Callable, Dict, Optional, Union, cast
from mypy.checker import TypeChecker
from mypy.nodes import MemberExpr, TypeInfo, NameExpr
from mypy.nodes import MemberExpr, NameExpr, TypeInfo
from mypy.options import Options
from mypy.plugin import AttributeContext, ClassDefContext, FunctionContext, MethodContext, Plugin
from mypy.types import AnyType, Instance, Type, TypeOfAny, TypeType, UnionType, CallableType, NoneTyp
from mypy.plugin import (
AttributeContext, ClassDefContext, FunctionContext, MethodContext, Plugin,
AnalyzeTypeContext)
from mypy.types import (
AnyType, CallableType, Instance, NoneTyp, Type, TypeOfAny, TypeType, UnionType,
)
from mypy_django_plugin import helpers, monkeypatch
from mypy_django_plugin.config import Config
from mypy_django_plugin.transformers import fields, init_create
from mypy_django_plugin.transformers.forms import make_meta_nested_class_inherit_from_any
from mypy_django_plugin.transformers.migrations import determine_model_cls_from_string_for_migrations, \
get_string_value_from_expr
from mypy_django_plugin.transformers.forms import (
make_meta_nested_class_inherit_from_any,
)
from mypy_django_plugin.transformers.migrations import (
determine_model_cls_from_string_for_migrations, get_string_value_from_expr,
)
from mypy_django_plugin.transformers.models import process_model_class
from mypy_django_plugin.transformers.settings import AddSettingValuesToDjangoConfObject, get_settings_metadata
from mypy_django_plugin.transformers.settings import (
AddSettingValuesToDjangoConfObject, get_settings_metadata,
)
def transform_model_class(ctx: ClassDefContext) -> None:
@@ -55,13 +66,32 @@ def determine_proper_manager_type(ctx: FunctionContext) -> Type:
if not isinstance(ret, Instance):
return ret
has_manager_base = False
for i, base in enumerate(ret.type.bases):
if base.type.fullname() in {helpers.MANAGER_CLASS_FULLNAME,
helpers.RELATED_MANAGER_CLASS_FULLNAME,
helpers.BASE_MANAGER_CLASS_FULLNAME}:
ret.type.bases[i] = Instance(base.type, [Instance(outer_model_info, [])])
return ret
return ret
has_manager_base = True
break
if has_manager_base:
# Fill in the manager's type argument from the outer model
new_type_args = [Instance(outer_model_info, [])]
return helpers.reparametrize_instance(ret, new_type_args)
else:
return ret
def set_first_generic_param_as_default_for_second(fullname: str, ctx: AnalyzeTypeContext) -> Type:
if not ctx.type.args:
return ctx.api.named_type(fullname, [AnyType(TypeOfAny.explicit),
AnyType(TypeOfAny.explicit)])
args = ctx.type.args
if len(args) == 1:
args = [args[0], args[0]]
analyzed_args = [ctx.api.analyze_type(arg) for arg in args]
return ctx.api.named_type(fullname, analyzed_args)
def return_user_model_hook(ctx: FunctionContext) -> Type:
@@ -250,6 +280,14 @@ class DjangoPlugin(Plugin):
else:
return {}
def _get_current_queryset_bases(self) -> Dict[str, int]:
model_sym = self.lookup_fully_qualified(helpers.QUERYSET_CLASS_FULLNAME)
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
return (helpers.get_django_metadata(model_sym.node)
.setdefault('queryset_bases', {helpers.QUERYSET_CLASS_FULLNAME: 1}))
else:
return {}
def get_function_hook(self, fullname: str
) -> Optional[Callable[[FunctionContext], Type]]:
if fullname == 'django.contrib.auth.get_user_model':
@@ -328,6 +366,14 @@ class DjangoPlugin(Plugin):
return extract_and_return_primary_key_of_bound_related_field_parameter
def get_type_analyze_hook(self, fullname: str
) -> Optional[Callable[[AnalyzeTypeContext], Type]]:
queryset_bases = self._get_current_queryset_bases()
if fullname in queryset_bases:
return partial(set_first_generic_param_as_default_for_second, fullname)
return None
def plugin(version):
return DjangoPlugin

View File

@@ -1,4 +0,0 @@
from .dependencies import (add_modules_as_a_source_seed_files,
inject_modules_as_dependencies_for_django_conf_settings,
restore_original_load_graph,
restore_original_dependencies_handling)

View File

@@ -3,9 +3,11 @@ from typing import Optional, cast
from mypy.checker import TypeChecker
from mypy.nodes import ListExpr, NameExpr, StrExpr, TupleExpr, TypeInfo, Var
from mypy.plugin import FunctionContext
from mypy.types import AnyType, CallableType, Instance, TupleType, Type, TypeOfAny, UnionType
from mypy.types import (
AnyType, CallableType, Instance, TupleType, Type, TypeOfAny, UnionType,
)
from mypy_django_plugin import helpers
from mypy_django_plugin.transformers.models import iter_over_assignments
def extract_referred_to_type(ctx: FunctionContext) -> Optional[Instance]:
@@ -99,10 +101,6 @@ def get_private_descriptor_type(type_info: TypeInfo, private_field_name: str, is
def set_descriptor_types_for_field(ctx: FunctionContext) -> Instance:
default_return_type = cast(Instance, ctx.default_return_type)
is_nullable = helpers.parse_bool(helpers.get_argument_by_name(ctx, 'null'))
if not is_nullable and default_return_type.type.has_base(helpers.CHAR_FIELD_FULLNAME):
# blank=True for CharField can be interpreted as null=True
is_nullable = helpers.parse_bool(helpers.get_argument_by_name(ctx, 'blank'))
set_type = get_private_descriptor_type(default_return_type.type, '_pyi_private_set_type',
is_nullable=is_nullable)
get_type = get_private_descriptor_type(default_return_type.type, '_pyi_private_get_type',
@@ -154,7 +152,7 @@ def record_field_properties_into_outer_model_class(ctx: FunctionContext) -> None
return
field_name = None
for name_expr, stmt in iter_over_assignments(outer_model.defn):
for name_expr, stmt in helpers.iter_over_assignments(outer_model.defn):
if stmt == ctx.context and isinstance(name_expr, NameExpr):
field_name = name_expr.name
break

View File

@@ -1,4 +1,5 @@
from mypy.plugin import ClassDefContext
from mypy_django_plugin import helpers

View File

@@ -4,8 +4,8 @@ from mypy.checker import TypeChecker
from mypy.nodes import TypeInfo, Var
from mypy.plugin import FunctionContext, MethodContext
from mypy.types import AnyType, Instance, Type, TypeOfAny
from mypy_django_plugin import helpers
from mypy_django_plugin.helpers import extract_field_setter_type, extract_explicit_set_type_of_model_primary_key, get_fields_metadata
from mypy_django_plugin.transformers.fields import get_private_descriptor_type
@@ -106,7 +106,7 @@ def redefine_and_typecheck_model_create(ctx: MethodContext) -> Type:
def extract_choices_type(model: TypeInfo, field_name: str) -> Optional[str]:
field_metadata = get_fields_metadata(model).get(field_name, {})
field_metadata = helpers.get_fields_metadata(model).get(field_name, {})
if 'choices' in field_metadata:
return field_metadata['choices']
return None
@@ -117,7 +117,7 @@ def extract_expected_types(ctx: FunctionContext, model: TypeInfo,
api = cast(TypeChecker, ctx.api)
expected_types: Dict[str, Type] = {}
primary_key_type = extract_explicit_set_type_of_model_primary_key(model)
primary_key_type = helpers.extract_explicit_set_type_of_model_primary_key(model)
if not primary_key_type:
# no explicit primary key, set pk to Any and add id
primary_key_type = AnyType(TypeOfAny.special_form)
@@ -143,7 +143,7 @@ def extract_expected_types(ctx: FunctionContext, model: TypeInfo,
expected_types[name + '_id'] = AnyType(TypeOfAny.from_unimported_type)
elif isinstance(typ, Instance):
field_type = extract_field_setter_type(typ)
field_type = helpers.extract_field_setter_type(typ)
if field_type is None:
continue
@@ -156,8 +156,9 @@ def extract_expected_types(ctx: FunctionContext, model: TypeInfo,
if is_nullable:
referred_to_model = helpers.make_required(typ.args[1])
if isinstance(referred_to_model, Instance) and referred_to_model.type.has_base(helpers.MODEL_CLASS_FULLNAME):
pk_type = extract_explicit_set_type_of_model_primary_key(referred_to_model.type)
if isinstance(referred_to_model, Instance) and referred_to_model.type.has_base(
helpers.MODEL_CLASS_FULLNAME):
pk_type = helpers.extract_explicit_set_type_of_model_primary_key(referred_to_model.type)
if not pk_type:
# extract set type of AutoField
autofield_info = api.lookup_typeinfo('django.db.models.fields.AutoField')
@@ -170,7 +171,7 @@ def extract_expected_types(ctx: FunctionContext, model: TypeInfo,
expected_types[name + '_id'] = related_primary_key_type
field_metadata = get_fields_metadata(model).get(name, {})
field_metadata = helpers.get_fields_metadata(model).get(name, {})
if field_type:
# related fields could be None in __init__ (but should be specified before save())
if helpers.has_any_of_bases(typ.type, (helpers.FOREIGN_KEY_FULLNAME,
@@ -178,7 +179,15 @@ def extract_expected_types(ctx: FunctionContext, model: TypeInfo,
field_type = helpers.make_optional(field_type)
# if primary_key=True and default specified
elif field_metadata.get('primary_key', False) and field_metadata.get('default_specified', False):
elif field_metadata.get('primary_key', False) and field_metadata.get('default_specified',
False):
field_type = helpers.make_optional(field_type)
# if CharField(blank=True,...) and not nullable, then field can be None in __init__
elif (
helpers.has_any_of_bases(typ.type, (helpers.CHAR_FIELD_FULLNAME,)) and is_init and
field_metadata.get('blank', False) and not field_metadata.get('null', False)
):
field_type = helpers.make_optional(field_type)
expected_types[name] = field_type

View File

@@ -4,6 +4,7 @@ from mypy.checker import TypeChecker
from mypy.nodes import Expression, StrExpr, TypeInfo
from mypy.plugin import MethodContext
from mypy.types import Instance, Type, TypeType
from mypy_django_plugin import helpers

View File

@@ -2,14 +2,16 @@ from abc import ABCMeta, abstractmethod
from typing import Dict, Iterator, List, Optional, Tuple, cast
import dataclasses
from mypy.nodes import ARG_STAR, ARG_STAR2, Argument, CallExpr, ClassDef, Expression, IndexExpr, \
Lvalue, MDEF, MemberExpr, MypyFile, NameExpr, StrExpr, SymbolTableNode, TypeInfo, Var
from mypy.nodes import (
ARG_STAR, ARG_STAR2, MDEF, Argument, CallExpr, ClassDef, Expression, IndexExpr, Lvalue, MemberExpr, MypyFile,
NameExpr, StrExpr, SymbolTableNode, TypeInfo, Var,
)
from mypy.plugin import ClassDefContext
from mypy.plugins.common import add_method
from mypy.semanal import SemanticAnalyzerPass2
from mypy.types import AnyType, Instance, NoneTyp, TypeOfAny
from mypy_django_plugin import helpers
from mypy_django_plugin.helpers import iter_over_assignments
@dataclasses.dataclass
@@ -40,7 +42,7 @@ class ModelClassInitializer(metaclass=ABCMeta):
var._fullname = self.model_classdef.info.fullname() + '.' + name
var.is_inferred = True
var.is_initialized_in_class = True
self.model_classdef.info.names[name] = SymbolTableNode(MDEF, var)
self.model_classdef.info.names[name] = SymbolTableNode(MDEF, var, plugin_generated=True)
@abstractmethod
def run(self) -> None:
@@ -48,7 +50,7 @@ class ModelClassInitializer(metaclass=ABCMeta):
def iter_call_assignments(klass: ClassDef) -> Iterator[Tuple[Lvalue, CallExpr]]:
for lvalue, rvalue in iter_over_assignments(klass):
for lvalue, rvalue in helpers.iter_over_assignments(klass):
if isinstance(rvalue, CallExpr):
yield lvalue, rvalue
@@ -56,7 +58,7 @@ def iter_call_assignments(klass: ClassDef) -> Iterator[Tuple[Lvalue, CallExpr]]:
def iter_over_one_to_n_related_fields(klass: ClassDef) -> Iterator[Tuple[NameExpr, CallExpr]]:
for lvalue, rvalue in iter_call_assignments(klass):
if (isinstance(lvalue, NameExpr)
and isinstance(rvalue.callee, MemberExpr)):
and isinstance(rvalue.callee, MemberExpr)):
if rvalue.callee.fullname in {helpers.FOREIGN_KEY_FULLNAME,
helpers.ONETOONE_FIELD_FULLNAME}:
yield lvalue, rvalue
@@ -107,8 +109,8 @@ class AddDefaultObjectsManager(ModelClassInitializer):
if isinstance(callee_expr, IndexExpr):
callee_expr = callee_expr.analyzed.expr
if isinstance(callee_expr, (MemberExpr, NameExpr)) \
and isinstance(callee_expr.node, TypeInfo) \
and callee_expr.node.has_base(helpers.BASE_MANAGER_CLASS_FULLNAME):
and isinstance(callee_expr.node, TypeInfo) \
and callee_expr.node.has_base(helpers.BASE_MANAGER_CLASS_FULLNAME):
managers.append((manager_name, callee_expr.node))
return managers
@@ -147,7 +149,7 @@ class AddIdAttributeIfPrimaryKeyTrueIsNotSet(ModelClassInitializer):
for _, rvalue in iter_call_assignments(self.model_classdef):
if ('primary_key' in rvalue.arg_names
and self.api.parse_bool(rvalue.args[rvalue.arg_names.index('primary_key')])):
and self.api.parse_bool(rvalue.args[rvalue.arg_names.index('primary_key')])):
break
else:
self.add_new_node_to_model_class('id', self.api.builtin_type('builtins.object'))
@@ -202,10 +204,10 @@ def is_related_field(expr: CallExpr, module_file: MypyFile) -> bool:
if isinstance(expr.callee, MemberExpr) and isinstance(expr.callee.expr, NameExpr):
module = module_file.names.get(expr.callee.expr.name)
if module \
and module.fullname == 'django.db.models' \
and expr.callee.name in {'ForeignKey',
'OneToOneField',
'ManyToManyField'}:
and module.fullname == 'django.db.models' \
and expr.callee.name in {'ForeignKey',
'OneToOneField',
'ManyToManyField'}:
return True
return False

View File

@@ -1,6 +1,8 @@
from typing import Iterable, List, Optional, cast
from mypy.nodes import ClassDef, Context, ImportAll, MypyFile, SymbolNode, SymbolTableNode, TypeInfo, Var
from mypy.nodes import (
ClassDef, Context, ImportAll, MypyFile, SymbolNode, SymbolTableNode, TypeInfo, Var,
)
from mypy.plugin import ClassDefContext
from mypy.semanal import SemanticAnalyzerPass2
from mypy.types import AnyType, Instance, NoneTyp, Type, TypeOfAny, UnionType
@@ -56,8 +58,8 @@ def load_settings_from_names(settings_classdef: ClassDef,
settings_classdef.info.names[name] = copied
else:
var = Var(name, AnyType(TypeOfAny.unannotated))
var.info = api.named_type('__builtins__.object').type
settings_classdef.info.names[name] = SymbolTableNode(sym.kind, var)
var.info = api.named_type('__builtins__.object').type # outer class type
settings_classdef.info.names[name] = SymbolTableNode(sym.kind, var, plugin_generated=True)
settings_metadata[name] = module.fullname()
@@ -67,11 +69,12 @@ def get_import_star_modules(api: SemanticAnalyzerPass2, module: MypyFile) -> Lis
for module_import in module.imports:
# relative import * are not resolved by mypy
if isinstance(module_import, ImportAll) and module_import.relative:
absolute_import_path, correct = correct_relative_import(module.fullname(), module_import.relative, module_import.id,
is_cur_package_init_file=False)
absolute_import_path, correct = correct_relative_import(module.fullname(), module_import.relative,
module_import.id, is_cur_package_init_file=False)
if not correct:
return []
for path in [absolute_import_path] + get_import_star_modules(api, module=api.modules.get(absolute_import_path)):
for path in [absolute_import_path] + get_import_star_modules(api,
module=api.modules.get(absolute_import_path)):
if path not in import_star_modules:
import_star_modules.append(path)
return import_star_modules

View File

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

View File

@@ -1,3 +1,4 @@
import itertools
import os
import re
import sys
@@ -37,7 +38,8 @@ IGNORED_ERRORS = {
# settings
re.compile(r'Module has no attribute "[A-Z_]+"'),
# attributes assigned to test functions
re.compile(r'"Callable\[(\[(Any(, )?)*((, )?VarArg\(Any\))?((, )?KwArg\(Any\))?\]|\.\.\.), Any\]" has no attribute'),
re.compile(
r'"Callable\[(\[(Any(, )?)*((, )?VarArg\(Any\))?((, )?KwArg\(Any\))?\]|\.\.\.), Any\]" has no attribute'),
# assign empty tuple
re.compile(r'Incompatible types in assignment \(expression has type "Tuple\[\]", '
r'variable has type "Tuple\[[A-Za-z, ]+\]"'),
@@ -54,8 +56,10 @@ IGNORED_ERRORS = {
'ValuesIterable',
'Value of type "Optional[Dict[str, Any]]" is not indexable',
'Argument 1 to "len" has incompatible type "Optional[List[_Record]]"; expected "Sized"',
'Argument 1 to "loads" has incompatible type "Union[bytes, str, None]"; expected "Union[str, bytes, bytearray]"',
'Incompatible types in assignment (expression has type "None", variable has type Module)'
'Argument 1 to "loads" has incompatible type "Union[bytes, str, None]"; '
+ 'expected "Union[str, bytes, bytearray]"',
'Incompatible types in assignment (expression has type "None", variable has type Module)',
'note:'
],
'admin_changelist': [
'Incompatible types in assignment (expression has type "FilteredChildAdmin", variable has type "ChildAdmin")'
@@ -65,8 +69,9 @@ IGNORED_ERRORS = {
],
'admin_widgets': [
'Incompatible types in assignment (expression has type "RelatedFieldWidgetWrapper", '
'variable has type "AdminRadioSelect")',
'Incompatible types in assignment (expression has type "Union[Widget, Any]", variable has type "AutocompleteSelect")'
+ 'variable has type "AdminRadioSelect")',
'Incompatible types in assignment (expression has type "Union[Widget, Any]", '
+ 'variable has type "AutocompleteSelect")'
],
'admin_utils': [
re.compile(r'Argument [0-9] to "lookup_field" has incompatible type'),
@@ -86,17 +91,10 @@ IGNORED_ERRORS = {
'Argument "is_dst" to "localize" of "BaseTzInfo" has incompatible type "None"; expected "bool"'
],
'aggregation': [
'Incompatible types in assignment (expression has type "QuerySet[Any]", variable has type "List[Any]")',
'"as_sql" undefined in superclass',
'Incompatible types in assignment (expression has type "FlatValuesListIterable", '
+ 'variable has type "ValuesListIterable")',
'Incompatible type for "contact" of "Book" (got "Optional[Author]", expected "Union[Author, Combinable]")',
'Incompatible type for "publisher" of "Book" (got "Optional[Publisher]", expected "Union[Publisher, Combinable]")'
],
'aggregation_regress': [
'Incompatible types in assignment (expression has type "List[str]", variable has type "QuerySet[Author]")',
'Incompatible types in assignment (expression has type "FlatValuesListIterable", variable has type "QuerySet[Any]")',
'Too few arguments for "count" of "Sequence"'
'Incompatible type for "publisher" of "Book" (got "Optional[Publisher]", '
+ 'expected "Union[Publisher, Combinable]")'
],
'apps': [
'Incompatible types in assignment (expression has type "str", target has type "type")',
@@ -152,9 +150,6 @@ IGNORED_ERRORS = {
'db_typecasts': [
'"object" has no attribute "__iter__"; maybe "__str__" or "__dir__"? (not iterable)'
],
'expressions': [
'Argument 1 to "Subquery" has incompatible type "Sequence[Dict[str, Any]]"; expected "QuerySet[Any]"'
],
'from_db_value': [
'has no attribute "vendor"'
],
@@ -176,8 +171,8 @@ IGNORED_ERRORS = {
'variable has type "SongForm"',
'"full_clean" of "BaseForm" does not return a value',
'No overload variant of "zip" matches argument types "Tuple[str, str, str]", "object"',
'note:',
'Incompatible types in assignment (expression has type "GetDateShowHiddenInitial", variable has type "GetDate")',
'Incompatible types in assignment (expression has type "GetDateShowHiddenInitial", '
+ 'variable has type "GetDate")',
re.compile(r'Incompatible types in assignment \(expression has type "[a-zA-Z]+Field", '
r'base class "BaseForm" defined the type as "Dict\[str, Any\]"\)'),
'List or tuple expected as variable arguments',
@@ -188,13 +183,13 @@ IGNORED_ERRORS = {
'Incompatible types in assignment (expression has type "TestForm", variable has type "Person")',
'Incompatible types in assignment (expression has type "Type[Textarea]", '
+ 'base class "Field" defined the type as "Widget")',
'Incompatible types in assignment (expression has type "SimpleUploadedFile", variable has type "BinaryIO")'
'Incompatible types in assignment (expression has type "SimpleUploadedFile", variable has type "BinaryIO")',
],
'get_object_or_404': [
'Argument 1 to "get_object_or_404" has incompatible type "str"; '
+ 'expected "Union[Type[<nothing>], Manager[<nothing>], QuerySet[<nothing>]]"',
+ 'expected "Union[Type[<nothing>], QuerySet[<nothing>, <nothing>]]"',
'Argument 1 to "get_list_or_404" has incompatible type "List[Type[Article]]"; '
+ 'expected "Union[Type[<nothing>], Manager[<nothing>], QuerySet[<nothing>]]"',
+ 'expected "Union[Type[<nothing>], QuerySet[<nothing>, <nothing>]]"',
'CustomClass'
],
'get_or_create': [
@@ -216,14 +211,10 @@ IGNORED_ERRORS = {
],
'lookup': [
'Unexpected keyword argument "headline__startswith" for "in_bulk" of "QuerySet"',
'note: '
],
'many_to_one': [
'Incompatible type for "parent" of "Child" (got "None", expected "Union[Parent, Combinable]")'
],
'model_inheritance_regress': [
'Incompatible types in assignment (expression has type "List[Supplier]", variable has type "QuerySet[Supplier]")'
],
'model_meta': [
'"object" has no attribute "items"',
'"Field" has no attribute "many_to_many"'
@@ -238,10 +229,10 @@ IGNORED_ERRORS = {
'Cannot assign multiple types to name "PersonTwoImages" without an explicit "Type[...]" annotation',
'Incompatible types in assignment (expression has type "Type[Person]", '
+ 'base class "ImageFieldTestMixin" defined the type as "Type[PersonWithHeightAndWidth]")',
'note: "Person" defined here'
],
'model_formsets': [
'Incompatible types in string interpolation (expression has type "object", placeholder has type "Union[int, float]")'
'Incompatible types in string interpolation (expression has type "object", '
+ 'placeholder has type "Union[int, float]")'
],
'model_formsets_regress': [
'Incompatible types in assignment (expression has type "Model", variable has type "User")'
@@ -287,16 +278,19 @@ IGNORED_ERRORS = {
'Incompatible types in assignment (expression has type "Type[Field[Any, Any]]',
'DummyArrayField',
'DummyJSONField',
'Argument "encoder" to "JSONField" has incompatible type "DjangoJSONEncoder"; expected "Optional[Type[JSONEncoder]]"',
'Argument "encoder" to "JSONField" has incompatible type "DjangoJSONEncoder"; '
+ 'expected "Optional[Type[JSONEncoder]]"',
'for model "CITestModel"',
'Incompatible type for "field" of "IntegerArrayModel" (got "None", expected "Union[Sequence[int], Combinable]")'
'Incompatible type for "field" of "IntegerArrayModel" (got "None", '
+ 'expected "Union[Sequence[int], Combinable]")'
],
'properties': [
re.compile('Unexpected attribute "(full_name|full_name_2)" for model "Person"')
],
'queries': [
'Incompatible types in assignment (expression has type "None", variable has type "str")',
'Invalid index type "Optional[str]" for "Dict[str, int]"; expected type "str"'
'Invalid index type "Optional[str]" for "Dict[str, int]"; expected type "str"',
'No overload variant of "values_list" of "QuerySet" matches argument types "str", "bool", "bool"',
],
'requests': [
'Incompatible types in assignment (expression has type "Dict[str, str]", variable has type "QueryDict")'
@@ -305,7 +299,7 @@ IGNORED_ERRORS = {
'Argument 1 to "TextIOWrapper" has incompatible type "HttpResponse"; expected "IO[bytes]"'
],
'prefetch_related': [
'Incompatible types in assignment (expression has type "List[Room]", variable has type "QuerySet[Room]")',
'Incompatible types in assignment (expression has type "List[Room]", variable has type "QuerySet[Room, Room]")',
'"None" has no attribute "__iter__"',
'has no attribute "read_by"'
],
@@ -334,11 +328,14 @@ IGNORED_ERRORS = {
'Incompatible types in assignment (expression has type "Thread", variable has type "Callable[[], Any]")'
],
'test_client': [
'Incompatible types in assignment (expression has type "StreamingHttpResponse", variable has type "HttpResponse")',
'Incompatible types in assignment (expression has type "HttpResponse", variable has type "StreamingHttpResponse")'
'Incompatible types in assignment (expression has type "StreamingHttpResponse", '
+ 'variable has type "HttpResponse")',
'Incompatible types in assignment (expression has type "HttpResponse", '
+ 'variable has type "StreamingHttpResponse")'
],
'test_client_regress': [
'Incompatible types in assignment (expression has type "Dict[<nothing>, <nothing>]", variable has type "SessionBase")',
'Incompatible types in assignment (expression has type "Dict[<nothing>, <nothing>]", '
+ 'variable has type "SessionBase")',
'Unsupported left operand type for + ("None")',
'Both left and right operands are unions'
],
@@ -348,7 +345,8 @@ IGNORED_ERRORS = {
'test_runner': [
'Value of type "TestSuite" is not indexable',
'"TestSuite" has no attribute "_tests"',
'Argument "result" to "run" of "TestCase" has incompatible type "RemoteTestResult"; expected "Optional[TestResult]"',
'Argument "result" to "run" of "TestCase" has incompatible type "RemoteTestResult"; '
+ 'expected "Optional[TestResult]"',
'Item "TestSuite" of "Union[TestCase, TestSuite]" has no attribute "id"',
'MockTestRunner',
'Incompatible types in assignment (expression has type "Tuple[Union[TestCase, TestSuite], ...]", '
@@ -631,7 +629,8 @@ def cd(path):
def is_ignored(line: str, test_folder_name: str) -> bool:
for pattern in IGNORED_ERRORS['__common__'] + IGNORED_ERRORS.get(test_folder_name, []):
for pattern in itertools.chain(IGNORED_ERRORS['__common__'],
IGNORED_ERRORS.get(test_folder_name, [])):
if isinstance(pattern, Pattern):
if pattern.search(line):
return True

29
setup.cfg Normal file
View File

@@ -0,0 +1,29 @@
[isort]
skip =
django-sources,
django-stubs,
test-data
include_trailing_comma = true
multi_line_output = 5
wrap_length = 120
[flake8]
exclude =
django-sources,
django-stubs,
test-data
max_line_length = 120
[tool:pytest]
testpaths = ./test-data
addopts =
--tb=native
--mypy-ini-file=./test-data/plugins.ini
-s
-v
[bdist_wheel]
universal = 1
[metadata]
license_file = LICENSE.txt

View File

@@ -21,7 +21,6 @@ with open('README.md', 'r') as f:
readme = f.read()
dependencies = [
'Django',
'mypy>=0.670',
'typing-extensions'
]
@@ -31,7 +30,7 @@ if sys.version_info[:2] < (3, 7):
setup(
name="django-stubs",
version="0.8.0",
version="0.9.0",
description='Django mypy stubs',
long_description=readme,
long_description_content_type='text/markdown',

View File

@@ -110,9 +110,22 @@ class MyModel(ParentModel):
reveal_type(MyModel().id) # E: Revealed type is 'uuid.UUID*'
[/CASE]
[CASE blank_for_charfield_is_the_same_as_null]
[CASE blank_and_null_char_field_allows_none]
from django.db import models
class MyModel(models.Model):
text = models.CharField(max_length=30, blank=True)
MyModel(text=None)
nulltext=models.CharField(max_length=1, blank=True, null=True)
MyModel(nulltext="")
MyModel(nulltext=None)
MyModel().nulltext=None
reveal_type(MyModel().nulltext) # E: Revealed type is 'Union[builtins.str, None]'
[/CASE]
[CASE blank_and_not_null_charfield_does_not_allow_none]
from django.db import models
class MyModel(models.Model):
notnulltext=models.CharField(max_length=1, blank=True, null=False)
MyModel(notnulltext=None) # Should allow None in constructor
MyModel(notnulltext="")
MyModel().notnulltext = None # E: Incompatible types in assignment (expression has type "None", variable has type "Union[str, int, Combinable]")
reveal_type(MyModel().notnulltext) # E: Revealed type is 'builtins.str*'
[/CASE]

View File

@@ -136,4 +136,66 @@ class AbstractBase2(models.Model):
class Child(AbstractBase1, AbstractBase2):
pass
[out]
[CASE managers_from_unrelated_models_dont_interfere]
from django.db import models
# Normal scenario where one model has a manager with an annotation of the same type as the model
class UnrelatedModel(models.Model):
objects = models.Manager[UnrelatedModel]()
class MyModel(models.Model):
pass
reveal_type(UnrelatedModel.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.UnrelatedModel]'
reveal_type(UnrelatedModel.objects.first()) # E: Revealed type is 'Union[main.UnrelatedModel*, None]'
reveal_type(MyModel.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.MyModel]'
reveal_type(MyModel.objects.first()) # E: Revealed type is 'Union[main.MyModel*, None]'
# Possible to specify objects without explicit annotation of models.Manager()
class UnrelatedModel2(models.Model):
objects = models.Manager()
class MyModel2(models.Model):
pass
reveal_type(UnrelatedModel2.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.UnrelatedModel2]'
reveal_type(UnrelatedModel2.objects.first()) # E: Revealed type is 'Union[main.UnrelatedModel2*, None]'
reveal_type(MyModel2.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.MyModel2]'
reveal_type(MyModel2.objects.first()) # E: Revealed type is 'Union[main.MyModel2*, None]'
# Inheritance works
class ParentOfMyModel3(models.Model):
objects = models.Manager()
class MyModel3(ParentOfMyModel3):
pass
reveal_type(ParentOfMyModel3.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.ParentOfMyModel3]'
reveal_type(ParentOfMyModel3.objects.first()) # E: Revealed type is 'Union[main.ParentOfMyModel3*, None]'
reveal_type(MyModel3.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.MyModel3]'
reveal_type(MyModel3.objects.first()) # E: Revealed type is 'Union[main.MyModel3*, None]'
# Inheritance works with explicit objects in child
class ParentOfMyModel4(models.Model):
objects = models.Manager()
class MyModel4(ParentOfMyModel4):
objects = models.Manager[MyModel4]()
reveal_type(ParentOfMyModel4.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.ParentOfMyModel4]'
reveal_type(ParentOfMyModel4.objects.first()) # E: Revealed type is 'Union[main.ParentOfMyModel4*, None]'
reveal_type(MyModel4.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.MyModel4]'
reveal_type(MyModel4.objects.first()) # E: Revealed type is 'Union[main.MyModel4*, None]'
[out]

View File

@@ -2,9 +2,94 @@
from django.db import models
class Blog(models.Model):
slug = models.CharField(max_length=100)
name = models.CharField(max_length=100)
created_at = models.DateTimeField()
# QuerySet where second type argument is not specified shouldn't raise any errors
class BlogQuerySet(models.QuerySet[Blog]):
pass
# Test that second type argument gets filled automatically
blog_qs: models.QuerySet[Blog]
reveal_type(blog_qs) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog, main.Blog]'
reveal_type(Blog.objects.in_bulk([1])) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
reveal_type(Blog.objects.in_bulk()) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
reveal_type(Blog.objects.in_bulk(['beatles_blog'], field_name='slug')) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
reveal_type(Blog.objects.in_bulk(['beatles_blog'], field_name='name')) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
# When ANDing QuerySets, the left-side's _Row parameter is used
reveal_type(Blog.objects.all() & Blog.objects.values()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, main.Blog*]'
reveal_type(Blog.objects.values() & Blog.objects.values()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict*[builtins.str, Any]]'
reveal_type(Blog.objects.values_list('id', 'name') & Blog.objects.values()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.tuple*[Any]]'
reveal_type(Blog.objects.values_list('id', 'name', named=True) & Blog.objects.values()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, typing.NamedTuple*]'
reveal_type(Blog.objects.values_list('id', flat=True) & Blog.objects.values()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Any]'
# .dates / .datetimes
reveal_type(Blog.objects.dates("created_at", "day")) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, datetime.date]'
reveal_type(Blog.objects.datetimes("created_at", "day")) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, datetime.datetime]'
qs = Blog.objects.all()
reveal_type(qs) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, main.Blog*]'
reveal_type(qs.get(id=1)) # E: Revealed type is 'main.Blog*'
reveal_type(iter(qs)) # E: Revealed type is 'typing.Iterator[main.Blog*]'
reveal_type(qs.iterator()) # E: Revealed type is 'typing.Iterator[main.Blog*]'
reveal_type(qs.first()) # E: Revealed type is 'Union[main.Blog*, None]'
reveal_type(qs.earliest()) # E: Revealed type is 'main.Blog*'
reveal_type(qs[0]) # E: Revealed type is 'main.Blog*'
reveal_type(qs[:9]) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, main.Blog*]'
reveal_type(qs.in_bulk()) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
values_qs = Blog.objects.values()
reveal_type(values_qs) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict[builtins.str, Any]]'
reveal_type(values_qs.all()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.get(id=1)) # E: Revealed type is 'builtins.dict*[builtins.str, Any]'
reveal_type(iter(values_qs)) # E: Revealed type is 'typing.Iterator[builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.iterator()) # E: Revealed type is 'typing.Iterator[builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.first()) # E: Revealed type is 'Union[builtins.dict*[builtins.str, Any], None]'
reveal_type(values_qs.earliest()) # E: Revealed type is 'builtins.dict*[builtins.str, Any]'
reveal_type(values_qs[0]) # E: Revealed type is 'builtins.dict*[builtins.str, Any]'
reveal_type(values_qs[:9]) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.in_bulk()) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
values_list_qs = Blog.objects.values_list('id', 'name')
reveal_type(values_list_qs) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.tuple[Any]]'
reveal_type(values_list_qs.all()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.tuple*[Any]]'
reveal_type(values_list_qs.get(id=1)) # E: Revealed type is 'builtins.tuple*[Any]'
reveal_type(iter(values_list_qs)) # E: Revealed type is 'typing.Iterator[builtins.tuple*[Any]]'
reveal_type(values_list_qs.iterator()) # E: Revealed type is 'typing.Iterator[builtins.tuple*[Any]]'
reveal_type(values_list_qs.first()) # E: Revealed type is 'Union[builtins.tuple*[Any], None]'
reveal_type(values_list_qs.earliest()) # E: Revealed type is 'builtins.tuple*[Any]'
reveal_type(values_list_qs[0]) # E: Revealed type is 'builtins.tuple*[Any]'
reveal_type(values_list_qs[:9]) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.tuple*[Any]]'
reveal_type(values_list_qs.in_bulk()) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
flat_values_list_qs = Blog.objects.values_list('id', flat=True)
reveal_type(flat_values_list_qs) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Any]'
reveal_type(flat_values_list_qs.all()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Any]'
reveal_type(flat_values_list_qs.get(id=1)) # E: Revealed type is 'Any'
reveal_type(iter(flat_values_list_qs)) # E: Revealed type is 'typing.Iterator[Any]'
reveal_type(flat_values_list_qs.iterator()) # E: Revealed type is 'typing.Iterator[Any]'
reveal_type(flat_values_list_qs.first()) # E: Revealed type is 'Union[Any, None]'
reveal_type(flat_values_list_qs.earliest()) # E: Revealed type is 'Any'
reveal_type(flat_values_list_qs[0]) # E: Revealed type is 'Any'
reveal_type(flat_values_list_qs[:9]) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Any]'
reveal_type(flat_values_list_qs.in_bulk()) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
named_values_list_qs = Blog.objects.values_list('id', named=True)
reveal_type(named_values_list_qs) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, typing.NamedTuple]'
reveal_type(named_values_list_qs.all()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, typing.NamedTuple*]'
reveal_type(named_values_list_qs.get(id=1)) # E: Revealed type is 'typing.NamedTuple*'
reveal_type(iter(named_values_list_qs)) # E: Revealed type is 'typing.Iterator[typing.NamedTuple*]'
reveal_type(named_values_list_qs.iterator()) # E: Revealed type is 'typing.Iterator[typing.NamedTuple*]'
reveal_type(named_values_list_qs.first()) # E: Revealed type is 'Union[typing.NamedTuple*, None]'
reveal_type(named_values_list_qs.earliest()) # E: Revealed type is 'typing.NamedTuple*'
reveal_type(named_values_list_qs[0]) # E: Revealed type is 'typing.NamedTuple*'
reveal_type(named_values_list_qs[:9]) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, typing.NamedTuple*]'
reveal_type(named_values_list_qs.in_bulk()) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
[out]