Skip to content

Commit b4cd975

Browse files
authored
Merge pull request #109 from mkurnikov/new-semanal
New semanal support, rewrite to use Django app registry
2 parents a9c1bcb + 1b6c337 commit b4cd975

File tree

102 files changed

+4897
-5341
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+4897
-5341
lines changed

Diff for: .gitignore

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ out/
55
/django
66
.idea/
77
.mypy_cache/
8-
django-sources
98
build/
109
dist/
11-
pip-wheel-metadata/
10+
pip-wheel-metadata/
11+
.pytest_cache/

Diff for: .gitmodules

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[submodule "django-sources"]
2+
path = django-sources
3+
url = https://github.com/django/django.git
4+
branch = stable/2.2.x

Diff for: .travis.yml

+9-16
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,17 @@ jobs:
1010
set -e
1111
pytest
1212
13-
- name: Run plugin test suite with python 3.6
13+
- name: Typecheck Django test suite with python 3.7
14+
python: 3.7
15+
script: 'python ./scripts/typecheck_tests.py'
16+
17+
- name: Typecheck Django test suite with python 3.6
1418
python: 3.6
15-
script: |
16-
set -e
17-
pytest
19+
script: 'python ./scripts/typecheck_tests.py'
1820

19-
- name: Typecheck Django test suite
21+
- name: Mypy for plugin code
2022
python: 3.7
21-
script: 'python ./scripts/typecheck_tests.py'
23+
script: 'mypy ./mypy_django_plugin'
2224

2325
- name: Lint with black
2426
python: 3.7
@@ -36,13 +38,4 @@ before_install: |
3638
# Upgrade pip, setuptools, and wheel
3739
pip install -U pip setuptools wheel
3840
install: |
39-
pip install -r ./dev-requirements.txt
40-
pip install -r ./scripts/typecheck-tests-requirements.txt
41-
42-
#deploy:
43-
# provider: pypi
44-
# user: "mkurnikov"
45-
# password:
46-
# secure: 0E+hkaIdtpEtyL1KZeglunZ5/PKjouFfa8ljakAwoig7VNUL+2sO/bTyg38wRQl0NvzDzEHSMEt1bzg4Tq7b7Zp6nLuewG/w7mGLzqaOlTySiPEfRsg8s6uO2KrTn7g9VhlXH6UtyTXoQdMt6aE8+bt/GmEesanS57NB2mhwmylFgQwlJFu4LfIv/+aGmc4eLeGI2Qhvs9QYf7qvYlLQldgFh8mAckQEEvaBg35sf+puypZgf4nkx1k/dfG9wnFWZU8PJ41LbMw/Wj+k/9NpF8ePwiAr0fvRMErZd8nvoiWjQQjhzgrLVHhXEP5pTHh3zjDuGFMWyKuBhC6WLsG4qOQz/HvxeYvNI+jaTp15BgxtefG/pCNDUl/8GlCde7xVt7xzEcYNJSRaZPY2oofEFSd9qDnr4kqmyCXpNsaHRHvkL61bFjXUcfOsMMYvQCC6N2Jjb7S97RbnDdkOZO/lnFhVANT2rigsaXlSlWyN6f7ApxDNvu6Ehu5yrx6IjlPZJ0sI9vvY3IoS6Fik7w9E6zjNVjbmUn1D4MKFP4v5ppNASOqYcZeLd42j8rjEp0gIc3ccz9aUIT9q8VqSXSdUbqA6SVwvHXIVPxJMXj0bqWBG1iKs0cPBuzRVpRrwkENWCSWElDAewM1qFEnK0LppyoYFbqoQ8F5FG0+re7QttKQ=
47-
# on:
48-
# tags: true
41+
pip install -r ./dev-requirements.txt

Diff for: README.md

+25-15
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ Could be run on earlier versions of Django, but expect some missing imports warn
1717
pip install django-stubs
1818
```
1919

20+
### WARNING: All configuration from pre-1.0.0 versions is dropped, use one below.
21+
22+
### WARNING: 1.0.0 breaks `dmypy`, if you need it, stay on the 0.12.x series.
23+
2024
To make mypy aware of the plugin, you need to add
2125

2226
```
@@ -27,27 +31,33 @@ plugins =
2731

2832
in your `mypy.ini` file.
2933

34+
Plugin requires Django settings module (what you put into `DJANGO_SETTINGS_MODULE` variable) to be specified inside `mypy.ini` file.
35+
```
36+
[mypy]
37+
strict_optional = True
3038
31-
## Configuration
32-
33-
In order to specify config file, set `MYPY_DJANGO_CONFIG` environment variable with path to the config file. Default is `./mypy_django.ini`
34-
35-
Config file format (.ini):
39+
; this one is new
40+
[mypy.plugins.django-stubs]
41+
django_settings_module = mysettings
3642
```
37-
[mypy_django_plugin]
43+
where `mysettings` is a value of `DJANGO_SETTINGS_MODULE` (with or without quotes)
44+
45+
New implementation uses Django runtime to extract models information, so it will crash, if your installed apps `models.py` is not correct. For this same reason, you cannot use `reveal_type` inside global scope of any Python file that will be executed for `django.setup()`.
3846

39-
# specify settings module to use for django.conf.settings, this setting
40-
# could also be specified with DJANGO_SETTINGS_MODULE environment variable
41-
# (it also takes priority over config file)
42-
django_settings = mysettings.local
47+
In other words, if your `manage.py runserver` crashes, mypy will crash too.
4348

44-
# if True, all unknown settings in django.conf.settings will fallback to Any,
45-
# specify it if your settings are loaded dynamically to avoid false positives
46-
ignore_missing_settings = True
49+
## Notes
4750

48-
# if True, unknown attributes on Model instances won't produce errors
49-
ignore_missing_model_attributes = True
51+
Implementation monkey-patches Django to add `__class_getitem__` to the `Manager` class. If you'd use Python3.7 and do that too in your code, you can make things like
5052
```
53+
class MyUserManager(models.Manager['MyUser']):
54+
pass
55+
class MyUser(models.Model):
56+
objects = UserManager()
57+
```
58+
work, which should make a error messages a bit better.
59+
60+
Otherwise, custom type will be created in mypy, named `MyUser__MyUserManager`, which will rewrite base manager as `models.Manager[User]` to make methods like `get_queryset()` and others return properly typed `QuerySet`.
5161

5262
## To get help
5363

Diff for: dev-requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
black
2-
pytest-mypy-plugins
2+
pytest-mypy-plugins==1.0.3
33
flake8
44
isort==4.3.4
55
-e .

Diff for: django-sources

Submodule django-sources added at 4d6449e

Diff for: django-stubs/apps/registry.pyi

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
1-
import collections
21
import threading
3-
from typing import Any, Callable, List, Optional, Tuple, Type, Union, Iterable, DefaultDict
2+
from collections import OrderedDict
3+
from typing import Any, Callable, List, Optional, Tuple, Type, Union, Iterable, DefaultDict, Dict
44

55
from django.db.migrations.state import AppConfigStub
66
from django.db.models.base import Model
77

88
from .config import AppConfig
99

1010
class Apps:
11-
all_models: collections.defaultdict = ...
12-
app_configs: collections.OrderedDict = ...
11+
all_models: "Dict[str, OrderedDict[str, Type[Model]]]" = ...
12+
app_configs: "OrderedDict[str, AppConfig]" = ...
1313
stored_app_configs: List[Any] = ...
1414
apps_ready: bool = ...
1515
ready_event: threading.Event = ...
1616
loading: bool = ...
1717
_pending_operations: DefaultDict[Tuple[str, str], List]
18-
def __init__(self, installed_apps: Optional[Union[List[AppConfigStub], List[str], Tuple]] = ...) -> None: ...
1918
models_ready: bool = ...
2019
ready: bool = ...
20+
def __init__(self, installed_apps: Optional[Union[List[AppConfigStub], List[str], Tuple]] = ...) -> None: ...
2121
def populate(self, installed_apps: Union[List[AppConfigStub], List[str], Tuple] = ...) -> None: ...
2222
def check_apps_ready(self) -> None: ...
2323
def check_models_ready(self) -> None: ...
2424
def get_app_configs(self) -> Iterable[AppConfig]: ...
2525
def get_app_config(self, app_label: str) -> AppConfig: ...
26-
def get_models(self, include_auto_created: bool = ..., include_swapped: bool = ...) -> List[Type[Model]]: ...
27-
def get_model(self, app_label: str, model_name: Optional[str] = ..., require_ready: bool = ...) -> Type[Model]: ...
26+
# it's not possible to support it in plugin properly now
27+
def get_models(self, include_auto_created: bool = ..., include_swapped: bool = ...) -> List[Type[Any]]: ...
28+
def get_model(self, app_label: str, model_name: Optional[str] = ..., require_ready: bool = ...) -> Type[Any]: ...
2829
def register_model(self, app_label: str, model: Type[Model]) -> None: ...
2930
def is_installed(self, app_name: str) -> bool: ...
3031
def get_containing_app_config(self, object_name: str) -> Optional[AppConfig]: ...

Diff for: django-stubs/contrib/auth/base_user.pyi

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
from typing import Any, Optional, Tuple, List, overload
1+
from typing import Any, Optional, Tuple, List, overload, TypeVar
2+
3+
from django.db.models.base import Model
24

35
from django.db import models
46

5-
class BaseUserManager(models.Manager):
7+
_T = TypeVar("_T", bound=Model)
8+
9+
class BaseUserManager(models.Manager[_T]):
610
@classmethod
711
def normalize_email(cls, email: Optional[str]) -> str: ...
812
def make_random_password(self, length: int = ..., allowed_chars: str = ...) -> str: ...
9-
def get_by_natural_key(self, username: Optional[str]) -> AbstractBaseUser: ...
13+
def get_by_natural_key(self, username: Optional[str]) -> _T: ...
1014

1115
class AbstractBaseUser(models.Model):
1216
password: models.CharField = ...

Diff for: django-stubs/contrib/auth/models.pyi

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
from typing import Any, Collection, Optional, Set, Tuple, Type, Union
1+
from typing import Any, Collection, Optional, Set, Tuple, Type, TypeVar
22

33
from django.contrib.auth.base_user import AbstractBaseUser as AbstractBaseUser, BaseUserManager as BaseUserManager
4+
from django.contrib.auth.validators import UnicodeUsernameValidator
45
from django.contrib.contenttypes.models import ContentType
6+
from django.db.models.base import Model
57
from django.db.models.manager import EmptyManager
68

7-
from django.contrib.auth.validators import UnicodeUsernameValidator
89
from django.db import models
910

1011
def update_last_login(sender: Type[AbstractBaseUser], user: AbstractBaseUser, **kwargs: Any) -> None: ...
@@ -27,13 +28,15 @@ class Group(models.Model):
2728
permissions: models.ManyToManyField = models.ManyToManyField(Permission)
2829
def natural_key(self): ...
2930

30-
class UserManager(BaseUserManager):
31+
_T = TypeVar("_T", bound=Model)
32+
33+
class UserManager(BaseUserManager[_T]):
3134
def create_user(
3235
self, username: str, email: Optional[str] = ..., password: Optional[str] = ..., **extra_fields: Any
33-
) -> AbstractUser: ...
36+
) -> _T: ...
3437
def create_superuser(
3538
self, username: str, email: Optional[str], password: Optional[str], **extra_fields: Any
36-
) -> AbstractBaseUser: ...
39+
) -> _T: ...
3740

3841
class PermissionsMixin(models.Model):
3942
is_superuser: models.BooleanField = ...

Diff for: django-stubs/contrib/contenttypes/fields.pyi

+9-26
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@ from django.db.models.fields.related import ForeignObject
77
from django.db.models.fields.related_descriptors import ReverseManyToOneDescriptor
88
from django.db.models.fields.reverse_related import ForeignObjectRel
99

10+
from django.db.models.expressions import Combinable
1011
from django.db.models.fields import Field, PositiveIntegerField
1112
from django.db.models.fields.mixins import FieldCacheMixin
1213
from django.db.models.query import QuerySet
1314
from django.db.models.query_utils import FilteredRelation, PathInfo
1415
from django.db.models.sql.where import WhereNode
1516

1617
class GenericForeignKey(FieldCacheMixin):
18+
# django-stubs implementation only fields
19+
_pyi_private_set_type: Union[Any, Combinable]
20+
_pyi_private_get_type: Any
21+
# attributes
1722
auto_created: bool = ...
1823
concrete: bool = ...
1924
editable: bool = ...
@@ -44,36 +49,21 @@ class GenericForeignKey(FieldCacheMixin):
4449
def get_prefetch_queryset(
4550
self, instances: Union[List[Model], QuerySet], queryset: Optional[QuerySet] = ...
4651
) -> Tuple[List[Model], Callable, Callable, bool, str, bool]: ...
47-
def __get__(
48-
self, instance: Optional[Model], cls: Type[Model] = ...
49-
) -> Optional[Union[GenericForeignKey, Model]]: ...
50-
def __set__(self, instance: Model, value: Optional[Model]) -> None: ...
52+
def __get__(self, instance: Optional[Model], cls: Type[Model] = ...) -> Optional[Any]: ...
53+
def __set__(self, instance: Model, value: Optional[Any]) -> None: ...
5154

5255
class GenericRel(ForeignObjectRel):
5356
field: GenericRelation
54-
limit_choices_to: Optional[Union[Dict[str, Any], Callable[[], Any]]]
55-
model: Type[Model]
56-
multiple: bool
57-
on_delete: Callable
58-
parent_link: bool
59-
related_name: str
60-
related_query_name: None
61-
symmetrical: bool
6257
def __init__(
6358
self,
6459
field: GenericRelation,
6560
to: Union[Type[Model], str],
66-
related_name: None = ...,
61+
related_name: Optional[str] = ...,
6762
related_query_name: Optional[str] = ...,
6863
limit_choices_to: Optional[Union[Dict[str, Any], Callable[[], Any]]] = ...,
6964
) -> None: ...
7065

7166
class GenericRelation(ForeignObject):
72-
auto_created: bool = ...
73-
many_to_many: bool = ...
74-
many_to_one: bool = ...
75-
one_to_many: bool = ...
76-
one_to_one: bool = ...
7767
rel_class: Any = ...
7868
mti_inherited: bool = ...
7969
object_id_field_name: Any = ...
@@ -90,23 +80,16 @@ class GenericRelation(ForeignObject):
9080
limit_choices_to: Optional[Union[Dict[str, Any], Callable[[], Any]]] = ...,
9181
**kwargs: Any
9282
) -> None: ...
93-
def check(self, **kwargs: Any) -> List[Error]: ...
9483
def resolve_related_fields(self) -> List[Tuple[PositiveIntegerField, Field]]: ...
9584
def get_path_info(self, filtered_relation: Optional[FilteredRelation] = ...) -> List[PathInfo]: ...
9685
def get_reverse_path_info(self, filtered_relation: None = ...) -> List[PathInfo]: ...
9786
def value_to_string(self, obj: Model) -> str: ...
98-
model: Any = ...
99-
def set_attributes_from_rel(self) -> None: ...
100-
def get_internal_type(self) -> str: ...
10187
def get_content_type(self) -> ContentType: ...
10288
def get_extra_restriction(
10389
self, where_class: Type[WhereNode], alias: Optional[str], remote_alias: str
10490
) -> WhereNode: ...
10591
def bulk_related_objects(self, objs: List[Model], using: str = ...) -> QuerySet: ...
10692

107-
class ReverseGenericManyToOneDescriptor(ReverseManyToOneDescriptor):
108-
field: GenericRelation
109-
rel: GenericRel
110-
def related_manager_cls(self): ...
93+
class ReverseGenericManyToOneDescriptor(ReverseManyToOneDescriptor): ...
11194

11295
def create_generic_related_manager(superclass: Any, rel: Any): ...

Diff for: django-stubs/contrib/postgres/fields/jsonb.pyi

+1-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ from django.db.models import Field
55
from django.db.models.lookups import Transform
66
from .mixins import CheckFieldDefaultMixin
77

8-
class JsonAdapter(object):
8+
class JsonAdapter:
99
encoder: Any = ...
1010
def __init__(self, adapted: Any, dumps: Optional[Any] = ..., encoder: Optional[Any] = ...) -> None: ...
1111
def dumps(self, obj: Any): ...
@@ -22,12 +22,7 @@ class JSONField(CheckFieldDefaultMixin, Field):
2222
encoder: Optional[Type[JSONEncoder]] = ...,
2323
**kwargs: Any
2424
) -> None: ...
25-
def db_type(self, connection: Any): ...
26-
def get_transform(self, name: Any): ...
27-
def get_prep_value(self, value: Any): ...
28-
def validate(self, value: Any, model_instance: Any) -> None: ...
2925
def value_to_string(self, obj: Any): ...
30-
def formfield(self, **kwargs: Any): ...
3126

3227
class KeyTransform(Transform):
3328
operator: str = ...

Diff for: django-stubs/contrib/sites/managers.pyi

+1-10
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
1-
from typing import Any, List, Optional
2-
3-
from django.core.checks.messages import Error
4-
from django.db.models.query import QuerySet
1+
from typing import Optional
52

63
from django.db import models
74

85
class CurrentSiteManager(models.Manager):
9-
creation_counter: int
10-
model: None
11-
name: None
12-
use_in_migrations: bool = ...
136
def __init__(self, field_name: Optional[str] = ...) -> None: ...
14-
def check(self, **kwargs: Any) -> List[Error]: ...
15-
def get_queryset(self) -> QuerySet: ...

Diff for: django-stubs/core/management/__init__.pyi

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
from typing import Any, Dict, List, Optional, Tuple, Union
1+
from typing import Any, Dict, List, Tuple, Union
22

3-
from django.core.management.base import BaseCommand as BaseCommand, CommandError as CommandError
3+
from .base import BaseCommand as BaseCommand, CommandError as CommandError
44

55
def find_commands(management_dir: str) -> List[str]: ...
66
def load_command_class(app_name: str, name: str) -> BaseCommand: ...
77
def get_commands() -> Dict[str, str]: ...
8-
def call_command(command_name: Union[Tuple[str], BaseCommand, str], *args: Any, **options: Any) -> Optional[str]: ...
8+
def call_command(command_name: Union[Tuple[str], BaseCommand, str], *args: Any, **options: Any) -> str: ...
99

1010
class ManagementUtility:
1111
argv: List[str] = ...

0 commit comments

Comments
 (0)