Skip to content

Commit b6e9bd5

Browse files
authored
Add support for newsemanal and django plugin 1.0.0 (#25)
* add support for newsemanal and django plugin 1.0.0 * add new linters to CI, fix setup.py * fix mypy errors * fix lint errors * isort fixes
1 parent 27afc14 commit b6e9bd5

Some content is hidden

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

41 files changed

+348
-744
lines changed

.gitignore

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,4 @@ out/
66
/django
77
.idea/
88
.mypy_cache
9-
drf-sources/
10-
pip-wheel-metadata/
11-
9+
pip-wheel-metadata/

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "drf-sources"]
2+
path = drf-sources
3+
url = https://github.com/encode/django-rest-framework.git

.travis.yml

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,34 @@ sudo: required
77

88
jobs:
99
include:
10-
- name: "run test suite with python 3.7"
11-
python: 3.7
12-
script: |
13-
set -e
14-
pytest
15-
16-
- name: "run test suite with python 3.6"
17-
python: 3.6
18-
script: |
19-
set -e
20-
pytest
21-
22-
- name: "Lint with black"
23-
python: 3.7
24-
script: |
25-
black --check --line-length=120 rest_framework-stubs/
10+
- name: Run plugin test suite with python 3.7
11+
python: 3.7
12+
script: |
13+
set -e
14+
pytest
15+
16+
- name: Run plugin test suite with python 3.6
17+
python: 3.6
18+
script: |
19+
set -e
20+
pytest
21+
22+
- name: Mypy for plugin code
23+
python: 3.7
24+
script: 'mypy ./mypy_drf_plugin'
25+
26+
- name: Lint plugin code with flake8
27+
python: 3.7
28+
script: 'flake8'
29+
30+
- name: Lint with black
31+
python: 3.7
32+
script: |
33+
black --check --line-length=120 rest_framework-stubs/
34+
35+
- name: Lint plugin code with isort
36+
python: 3.7
37+
script: 'isort --check'
2638

2739
before_install: |
2840
# Upgrade pip, setuptools, and wheel

dev-requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
black
2-
-e git+https://github.com/mkurnikov/pytest-mypy-plugins.git#egg=pytest-mypy-plugins
2+
pytest-mypy-plugins==1.0.3
3+
flake8==3.7.8
4+
isort==4.3.4
35
-e .

drf-sources

Submodule drf-sources added at 659375f

mypy.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[mypy]
2+
ignore_missing_imports = True

mypy_drf_plugin/helpers.py

Lines changed: 0 additions & 142 deletions
This file was deleted.

mypy_drf_plugin/lib/__init__.py

Whitespace-only changes.

mypy_drf_plugin/lib/fullnames.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
FIELD_FULLNAME = 'rest_framework.fields.Field'
2+
BASE_SERIALIZER_FULLNAME = 'rest_framework.serializers.BaseSerializer'
3+
SERIALIZER_FULLNAME = 'rest_framework.serializers.Serializer'
4+
LIST_SERIALIZER_FULLNAME = 'rest_framework.serializers.ListSerializer'
5+
MODEL_SERIALIZER_FULLNAME = 'rest_framework.serializers.ModelSerializer'
6+
7+
# TODO: finish mapping
8+
SERIALIZER_FIELD_MAPPING = {
9+
'django.db.models.fields.AutoField': 'rest_framework.serializers.IntegerField',
10+
'django.db.models.fields.BigIntegerField': 'rest_framework.serializers.IntegerField',
11+
'django.db.models.fields.BooleanField': 'rest_framework.serializers.BooleanField',
12+
'django.db.models.fields.CharField': 'rest_framework.serializers.CharField',
13+
'django.db.models.fields.CommaSeparatedIntegerField': 'rest_framework.serializers.CharField',
14+
'django.db.models.fields.DateField': 'rest_framework.serializers.DateField',
15+
'django.db.models.fields.DateTimeField': 'rest_framework.serializers.DateTimeField',
16+
'django.db.models.fields.DecimalField': 'rest_framework.serializers.DecimalField',
17+
}
18+
ID_TYPE = 'builtins.object'

mypy_drf_plugin/lib/helpers.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from typing import Any, Dict
2+
3+
from mypy.nodes import TypeInfo
4+
5+
6+
def get_drf_metadata(info: TypeInfo) -> Dict[str, Any]:
7+
return info.metadata.setdefault('drf', {})

mypy_drf_plugin/main.py

Lines changed: 21 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,45 @@
1-
from typing import Callable, Dict, Optional, Sequence
1+
from typing import Callable, Dict, Optional
22

33
from mypy.nodes import TypeInfo
44
from mypy.options import Options
5-
from mypy.plugin import AttributeContext, ClassDefContext, FunctionContext, MethodContext, Plugin
6-
from mypy.types import Instance, Type
5+
from mypy.plugin import ClassDefContext, Plugin
6+
from mypy_django_plugin import main as mypy_django_main
7+
from mypy_django_plugin.django.context import DjangoContext
78

8-
from mypy_drf_plugin import helpers, monkeypatch
9-
from mypy_drf_plugin.transformers import fields, serializers, validation
9+
from mypy_drf_plugin.lib import fullnames, helpers
10+
from mypy_drf_plugin.transformers import serializers
1011

1112

12-
def extract_base_model_fullname(serializer_type: Type) -> Optional[str]:
13-
if not isinstance(serializer_type, Instance):
14-
return None
15-
16-
base_model_fullname = helpers.get_drf_metadata(serializer_type.type).get('base_model')
17-
if not base_model_fullname:
18-
return None
19-
20-
return base_model_fullname
21-
22-
23-
def get_instance_of_model_bound_to_serializer(ctx: MethodContext) -> Type:
24-
base_model_fullname = extract_base_model_fullname(ctx.type)
25-
if not base_model_fullname:
26-
return ctx.default_return_type
27-
28-
try:
29-
return ctx.api.named_generic_type(base_model_fullname, [])
30-
except AssertionError: # cannot find a model with name
31-
return ctx.default_return_type
13+
def transform_serializer_class(ctx: ClassDefContext) -> None:
14+
sym = ctx.api.lookup_fully_qualified_or_none(fullnames.BASE_SERIALIZER_FULLNAME)
15+
if sym is not None and isinstance(sym.node, TypeInfo):
16+
helpers.get_drf_metadata(sym.node)['serializer_bases'][ctx.cls.fullname] = 1
3217

18+
serializers.make_meta_nested_class_inherit_from_any(ctx)
3319

34-
class BaseClassTransformers:
35-
def __init__(self, hooks: Sequence[Callable]):
36-
self.hooks = hooks
3720

38-
def __call__(self, ctx: ClassDefContext) -> None:
39-
for hook in self.hooks:
40-
hook(ctx)
41-
42-
43-
class DRFPlugin(Plugin):
21+
class NewSemanalDRFPlugin(Plugin):
4422
def __init__(self, options: Options) -> None:
4523
super().__init__(options)
46-
monkeypatch.make_field_repr_not_return_any_generics()
24+
25+
django_settings_module = mypy_django_main.extract_django_settings_module(options.config_file)
26+
self.django_context = DjangoContext(django_settings_module)
4727

4828
def _get_currently_defined_serializers(self) -> Dict[str, int]:
49-
base_serializer_sym = self.lookup_fully_qualified(helpers.BASE_SERIALIZER_FULLNAME)
29+
base_serializer_sym = self.lookup_fully_qualified(fullnames.BASE_SERIALIZER_FULLNAME)
5030
if base_serializer_sym is not None and isinstance(base_serializer_sym.node, TypeInfo):
5131
return (base_serializer_sym.node.metadata
5232
.setdefault('drf', {})
53-
.setdefault('serializer_bases', {helpers.BASE_SERIALIZER_FULLNAME: 1,
54-
helpers.MODEL_SERIALIZER_FULLNAME: 1,
55-
helpers.SERIALIZER_FULLNAME: 1}))
56-
else:
57-
return {}
58-
59-
def _get_currently_defined_list_serializers(self) -> Dict[str, int]:
60-
list_serializer_sym = self.lookup_fully_qualified(helpers.LIST_SERIALIZER_FULLNAME)
61-
if list_serializer_sym is not None and isinstance(list_serializer_sym.node, TypeInfo):
62-
return (helpers.get_drf_metadata(list_serializer_sym.node)
63-
.setdefault('list_serializer_bases', {helpers.LIST_SERIALIZER_FULLNAME: 1}))
33+
.setdefault('serializer_bases', {fullnames.BASE_SERIALIZER_FULLNAME: 1}))
6434
else:
6535
return {}
6636

67-
def _get_defined_serializer_base_classes(self) -> Dict[str, int]:
68-
return {**self._get_currently_defined_serializers(), **self._get_currently_defined_list_serializers()}
69-
70-
def _get_defined_field_base_classes(self) -> Dict[str, int]:
71-
base_field_sym = self.lookup_fully_qualified(helpers.FIELD_FULLNAME)
72-
if base_field_sym is not None and isinstance(base_field_sym.node, TypeInfo):
73-
return (helpers.get_drf_metadata(base_field_sym.node)
74-
.setdefault('field_bases', {helpers.FIELD_FULLNAME: 1}))
75-
else:
76-
return {}
77-
78-
def get_function_hook(self, fullname: str
79-
) -> Optional[Callable[[FunctionContext], Type]]:
80-
sym = self.lookup_fully_qualified(fullname)
81-
if sym is not None and isinstance(sym.node, TypeInfo):
82-
if sym.node.has_base(helpers.FIELD_FULLNAME):
83-
return fields.set_generic_parameters_for_field
84-
85-
def get_method_hook(self, fullname: str
86-
) -> Optional[Callable[[MethodContext], Type]]:
87-
class_name, _, method_name = fullname.rpartition('.')
88-
if method_name == 'to_representation':
89-
if class_name in self._get_currently_defined_serializers():
90-
return validation.return_typeddict_from_to_representation
91-
92-
if class_name in self._get_currently_defined_list_serializers():
93-
return validation.return_list_of_typeddict_for_list_serializer_from_to_representation
94-
95-
if method_name in {'to_internal_value', 'run_validation'}:
96-
if class_name in self._get_currently_defined_serializers():
97-
return validation.return_typeddict_from_to_internal_value
98-
99-
if class_name in self._get_currently_defined_list_serializers():
100-
return validation.return_list_of_typeddict_for_list_serializer_from_to_internal_value
101-
102-
if method_name in {'create', 'save'}:
103-
if class_name in self._get_currently_defined_serializers():
104-
return get_instance_of_model_bound_to_serializer
105-
10637
def get_base_class_hook(self, fullname: str
10738
) -> Optional[Callable[[ClassDefContext], None]]:
108-
if fullname in self._get_defined_serializer_base_classes():
109-
return BaseClassTransformers([serializers.transform_serializer_class,
110-
fields.set_types_metadata])
111-
112-
if fullname in self._get_defined_field_base_classes():
113-
return fields.set_types_metadata
39+
if fullname in self._get_currently_defined_serializers():
40+
return transform_serializer_class
41+
return None
11442

11543

11644
def plugin(version):
117-
return DRFPlugin
45+
return NewSemanalDRFPlugin

0 commit comments

Comments
 (0)