From 371caaf538fc02b6aaef1d750174e75fae1bd342 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Fri, 11 Oct 2019 20:39:39 +0300 Subject: [PATCH] add mypy setup to tox.ini --- mypy.ini | 2 ++ requirements/requirements-mypy.txt | 1 + rest_framework/compat.py | 6 +++--- rest_framework/fields.py | 15 ++++++++------- rest_framework/parsers.py | 3 ++- rest_framework/relations.py | 5 +++-- rest_framework/renderers.py | 7 ++++--- rest_framework/serializers.py | 3 ++- rest_framework/test.py | 4 ++-- rest_framework/throttling.py | 3 ++- rest_framework/views.py | 5 ++++- tox.ini | 8 +++++++- 12 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 mypy.ini create mode 100644 requirements/requirements-mypy.txt diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 000000000..1215375ed --- /dev/null +++ b/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +ignore_missing_imports = True \ No newline at end of file diff --git a/requirements/requirements-mypy.txt b/requirements/requirements-mypy.txt new file mode 100644 index 000000000..040c3845c --- /dev/null +++ b/requirements/requirements-mypy.txt @@ -0,0 +1 @@ +mypy==0.730 \ No newline at end of file diff --git a/rest_framework/compat.py b/rest_framework/compat.py index df100966b..f13a854db 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -117,14 +117,14 @@ except ImportError: try: import yaml except ImportError: - yaml = None + yaml = None # type: ignore # requests is optional try: import requests except ImportError: - requests = None + requests = None # type: ignore # PATCH method is not implemented by Django @@ -156,7 +156,7 @@ try: md_filter_add_syntax_highlight(md) return md.convert(text) except ImportError: - apply_markdown = None + apply_markdown = None # type: ignore markdown = None diff --git a/rest_framework/fields.py b/rest_framework/fields.py index c0ceebe3b..37bd46874 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -7,6 +7,7 @@ import re import uuid from collections import OrderedDict from collections.abc import Mapping +from typing import Callable, List, Any, Optional, Dict from django.conf import settings from django.core.exceptions import ObjectDoesNotExist @@ -302,9 +303,9 @@ class Field: 'required': _('This field is required.'), 'null': _('This field may not be null.') } - default_validators = [] - default_empty_html = empty - initial = None + default_validators = [] # type: List[Callable[[Any], None]] + default_empty_html = empty # type: Optional[Any] + initial = None # type: Optional[Any] def __init__(self, read_only=False, write_only=False, required=None, default=empty, initial=empty, source=None, @@ -1456,7 +1457,7 @@ class MultipleChoiceField(ChoiceField): 'not_a_list': _('Expected a list of items but got type "{input_type}".'), 'empty': _('This selection may not be empty.') } - default_empty_html = [] + default_empty_html = [] # type: List[Any] def __init__(self, *args, **kwargs): self.allow_empty = kwargs.pop('allow_empty', True) @@ -1597,7 +1598,7 @@ class _UnvalidatedField(Field): class ListField(Field): child = _UnvalidatedField() - initial = [] + initial = [] # type: List[Any] default_error_messages = { 'not_a_list': _('Expected a list of items but got type "{input_type}".'), 'empty': _('This list may not be empty.'), @@ -1675,8 +1676,8 @@ class ListField(Field): class DictField(Field): - child = _UnvalidatedField() - initial = {} + child = _UnvalidatedField() # type: Field + initial = {} # type: Dict[str, Any] default_error_messages = { 'not_a_dict': _('Expected a dictionary of items but got type "{input_type}".'), 'empty': _('This dictionary may not be empty.'), diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index fc4eb1428..2f7d1c968 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -5,6 +5,7 @@ They give us a generic way of being able to handle various media types on the request, such as form content or json encoded data. """ import codecs +from typing import Optional from urllib import parse from django.conf import settings @@ -33,7 +34,7 @@ class BaseParser: All parsers should extend `BaseParser`, specifying a `media_type` attribute, and overriding the `.parse()` method. """ - media_type = None + media_type = None # type: Optional[str] def parse(self, stream, media_type=None, parser_context=None): """ diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 338776884..c8549052b 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -1,5 +1,6 @@ import sys from collections import OrderedDict +from typing import List, Any from urllib import parse from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist @@ -472,8 +473,8 @@ class ManyRelatedField(Field): You shouldn't generally need to be using this class directly yourself, and should instead simply set 'many=True' on the relationship. """ - initial = [] - default_empty_html = [] + initial = [] # type: List[Any] + default_empty_html = [] # type: List[Any] default_error_messages = { 'not_a_list': _('Expected a list of items but got type "{input_type}".'), 'empty': _('This list may not be empty.') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 9a6f3c3c5..e8b6f945d 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -8,6 +8,7 @@ REST framework also provides an HTML renderer that renders the browsable API. """ import base64 from collections import OrderedDict +from typing import Optional from urllib import parse from django import forms @@ -42,9 +43,9 @@ class BaseRenderer: All renderers should extend this class, setting the `media_type` and `format` attributes, and override the `.render()` method. """ - media_type = None - format = None - charset = 'utf-8' + media_type = None # type: Optional[str] + format = None # type: Optional[str] + charset = 'utf-8' # type: Optional[str] render_style = 'text' def render(self, data, accepted_media_type=None, renderer_context=None): diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index f5d9a5065..5c82d09d4 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -15,6 +15,7 @@ import inspect import traceback from collections import OrderedDict from collections.abc import Mapping +from typing import Type from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured from django.core.exceptions import ValidationError as DjangoValidationError @@ -891,7 +892,7 @@ class ModelSerializer(Serializer): } if ModelDurationField is not None: serializer_field_mapping[ModelDurationField] = DurationField - serializer_related_field = PrimaryKeyRelatedField + serializer_related_field = PrimaryKeyRelatedField # type: Type[RelatedField] serializer_related_to_field = SlugRelatedField serializer_url_field = HyperlinkedIdentityField serializer_choice_field = ChoiceField diff --git a/rest_framework/test.py b/rest_framework/test.py index ab16c2787..4d0941748 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -115,7 +115,7 @@ if requests is not None: return super().request(method, url, *args, **kwargs) else: - def RequestsClient(*args, **kwargs): + def RequestsClient(*args, **kwargs): # type: ignore raise ImproperlyConfigured('requests must be installed in order to use RequestsClient.') @@ -131,7 +131,7 @@ if coreapi is not None: return self._session else: - def CoreAPIClient(*args, **kwargs): + def CoreAPIClient(*args, **kwargs): # type: ignore raise ImproperlyConfigured('coreapi must be installed in order to use CoreAPIClient.') diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 0ba2ba66b..d5d0de57a 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -2,6 +2,7 @@ Provides various throttling policies. """ import time +from typing import Optional from django.core.cache import cache as default_cache from django.core.exceptions import ImproperlyConfigured @@ -62,7 +63,7 @@ class SimpleRateThrottle(BaseThrottle): cache = default_cache timer = time.time cache_format = 'throttle_%(scope)s_%(ident)s' - scope = None + scope = None # type: Optional[str] THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES def __init__(self): diff --git a/rest_framework/views.py b/rest_framework/views.py index bec10560a..c0f421534 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -1,6 +1,8 @@ """ Provides an APIView class that is the base of all views in REST framework. """ +from typing import Optional + from django.conf import settings from django.core.exceptions import PermissionDenied from django.db import connection, models, transaction @@ -15,6 +17,7 @@ from rest_framework import exceptions, status from rest_framework.request import Request from rest_framework.response import Response from rest_framework.schemas import DefaultSchema +from rest_framework.schemas.inspectors import ViewInspector from rest_framework.settings import api_settings from rest_framework.utils import formatting @@ -116,7 +119,7 @@ class APIView(View): # Allow dependency injection of other settings to make testing easier. settings = api_settings - schema = DefaultSchema() + schema = DefaultSchema() # type: Optional[ViewInspector] @classmethod def as_view(cls, **initkwargs): diff --git a/tox.ini b/tox.ini index 699ca909c..2c5aac5d1 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ envlist = {py35,py36,py37}-django21 {py35,py36,py37}-django22 {py36,py37}-djangomaster, - base,dist,lint,docs, + base,dist,lint,docs,mypy, [travis:env] DJANGO = @@ -57,3 +57,9 @@ commands = mkdocs build deps = -rrequirements/requirements-testing.txt -rrequirements/requirements-documentation.txt + +[testenv:mypy] +basepython = python3.7 +commands = mypy ./rest_framework +deps = + -rrequirements/requirements-mypy.txt