Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync, update and debug against DRF #94

Merged
merged 58 commits into from Oct 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
35ff3a3
resolved flake8 issues
Oct 3, 2020
7c22d5d
Revert "resolved flake8 issues"
Oct 3, 2020
fdb98bf
addressed review comments
Oct 3, 2020
1b93396
resolved flake8 issues
Oct 3, 2020
48ebeeb
Added missing stubs and data
Oct 3, 2020
fc03caf
fixed tests
Oct 3, 2020
0eecdbc
applied black and isort
Oct 3, 2020
12ff4c0
converted Literal import from typing to typing_extensions
Oct 3, 2020
b56e435
updated compat
Oct 3, 2020
2e5343f
second pass
Oct 3, 2020
86fcdbc
Update relations.pyi
Goldziher Oct 3, 2020
b8050d6
Update utils.pyi
Goldziher Oct 3, 2020
b85b9c6
Update utils.pyi
Goldziher Oct 3, 2020
feafdc9
Update serializers.pyi
Goldziher Oct 3, 2020
3349b07
refined typings
Oct 4, 2020
094e0c8
fixed schema typings
Oct 4, 2020
4319eaa
fix wrong typing of request
Oct 4, 2020
29c0ece
added functional testing script
Oct 4, 2020
1fa63ec
updated configs
Oct 4, 2020
0cd27bd
functioning version
Oct 4, 2020
3cff1cb
updated implementation
Oct 5, 2020
56a2414
fixed issues following drf test
Oct 5, 2020
612db30
updated serializer typing to use generics
Oct 5, 2020
b78de2a
fixed relations and permissions
Oct 6, 2020
01d7cdd
fixed parsers
Oct 6, 2020
dd01277
fixed pagination
Oct 6, 2020
89dc5ef
fixed serializers
Oct 6, 2020
890d275
update fields
Oct 6, 2020
6aa0fab
updated fields and added protocol to serializer
Oct 7, 2020
12b8a9a
fixed field typing to use generic
Oct 7, 2020
8477a7b
updated relations
Oct 7, 2020
d69e189
finished serializers
Oct 8, 2020
cc61bbc
finished fields
Oct 8, 2020
7b66e45
fixed decorators
Oct 8, 2020
1768f14
fixed authtoken
Oct 8, 2020
9c96592
fixed generic relations
Oct 8, 2020
affa2c2
fixed mypy detected bugs
Oct 8, 2020
f551193
finished schema
Oct 9, 2020
eb49884
finished fixing all drf related errors
Oct 9, 2020
1330237
cleanup
Oct 9, 2020
b99c47f
fix mypy.ini
Oct 9, 2020
51f0f31
fixed tests
Oct 9, 2020
0f83965
updated travis settings
Oct 9, 2020
dc8c30e
updated readme
Oct 9, 2020
74b96ba
temporarily disabled new travis config
Oct 9, 2020
0a5ff73
fixed python 3.6 comp. issues
Oct 9, 2020
cd3c16d
re-enabled full travis config
Oct 9, 2020
5f67e7b
address has_object_permission review comment
Oct 9, 2020
e113c88
fixed version comp issue
Oct 9, 2020
0bfe1da
address permission review comment
Oct 9, 2020
16ec6bd
applied black and isort
Oct 9, 2020
1ec1a6f
update typecheck script to use distutils
Oct 9, 2020
ceaf736
isorted
Oct 9, 2020
810454a
cleanup
Oct 9, 2020
dabd437
Update settings.pyi
Goldziher Oct 9, 2020
8d79794
addressed review comments
Oct 9, 2020
efc5830
Merge branch 'sync-with-drf' of https://github.com/Goldziher/djangore…
Oct 9, 2020
f3b0a7e
Update decorators.pyi
Goldziher Oct 9, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -8,3 +8,5 @@
__pycache__/
out/
pip-wheel-metadata/
drf_source/
.DS_Store
14 changes: 13 additions & 1 deletion .travis.yml
Expand Up @@ -7,6 +7,18 @@ sudo: required

jobs:
include:
- name: Run typecheck on DRF master test suite with Python 3.8
python: 3.8
script: python ./scripts/typecheck_tests.py

- name: Run typecheck on DRF master test suite with Python 3.7
python: 3.7
script: python ./scripts/typecheck_tests.py

- name: Run typecheck on DRF master test suite with Python 3.6
python: 3.6
script: python ./scripts/typecheck_tests.py

- name: Run plugin test suite with python 3.8
python: 3.8
script: pytest
Expand Down Expand Up @@ -40,7 +52,7 @@ jobs:

before_install: |
# Upgrade pip, setuptools, and wheel
pip install -U pip setuptools wheel
pip install -U pip setuptools wheel djangorestframework

install: |
pip install -r ./dev-requirements.txt
6 changes: 2 additions & 4 deletions CONTRIBUTING.md
Expand Up @@ -55,12 +55,11 @@ To run unit tests:
pytest
```

Type-related tests ensure that different Django versions do work correctly.
Type-related tests ensure that different DRF versions do work correctly.
To run type-related tests:

```bash
python ./scripts/typecheck_tests.py --django_version=2.2
python ./scripts/typecheck_tests.py --django_version=3.0
python ./scripts/typecheck_tests.py --drf_version=3.12.1
```

Currently we only support two Django versions.
Expand All @@ -77,7 +76,6 @@ black rest_framework-stubs
To run linting:

```bash
flake8
flake8 --config flake8-pyi.ini
```

Expand Down
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -7,8 +7,8 @@
[![Gitter](https://badges.gitter.im/mypy-django/Lobby.svg)](https://gitter.im/mypy-django/Lobby)


Mypy stubs for [DRF 3.9.x](https://pypi.org/project/djangorestframework/).
Supports Python 3.6 and 3.7.
Mypy stubs for [DRF 3.12.x](https://pypi.org/project/djangorestframework/).
Supports Python 3.6, 3.7 and 3.8.

## Installation

Expand Down
4 changes: 4 additions & 0 deletions dev-requirements.txt
Expand Up @@ -4,4 +4,8 @@ flake8==3.8.3
isort==5.5.4
mypy==0.782
pytest==5.4.3
requests==2.24.0
coreapi==2.3.3
gitpython==3.1.9
psycopg2-binary
-e .
22 changes: 20 additions & 2 deletions mypy_drf_plugin/lib/fullnames.py
Expand Up @@ -3,8 +3,6 @@
SERIALIZER_FULLNAME = 'rest_framework.serializers.Serializer'
LIST_SERIALIZER_FULLNAME = 'rest_framework.serializers.ListSerializer'
MODEL_SERIALIZER_FULLNAME = 'rest_framework.serializers.ModelSerializer'

# TODO: finish mapping
SERIALIZER_FIELD_MAPPING = {
'django.db.models.fields.AutoField': 'rest_framework.serializers.IntegerField',
'django.db.models.fields.BigIntegerField': 'rest_framework.serializers.IntegerField',
Expand All @@ -14,5 +12,25 @@
'django.db.models.fields.DateField': 'rest_framework.serializers.DateField',
'django.db.models.fields.DateTimeField': 'rest_framework.serializers.DateTimeField',
'django.db.models.fields.DecimalField': 'rest_framework.serializers.DecimalField',
'django.db.models.fields.DurationField': 'rest_framework.serializers.DurationField',
'django.db.models.fields.EmailField': 'rest_framework.serializers.EmailField',
'django.db.models.fields.Field': 'rest_framework.serializers.ModelField',
'django.db.models.fields.FileField': 'rest_framework.serializers.FileField',
'django.db.models.fields.FilePathField': 'rest_framework.serializers.FilePathField',
'django.db.models.fields.FloatField': 'rest_framework.serializers.FloatField',
'django.db.models.fields.GenericIPAddressField': 'rest_framework.serializers.IPAddressField',
'django.db.models.fields.ImageField': 'rest_framework.serializers.ImageField',
'django.db.models.fields.IntegerField': 'rest_framework.serializers.IntegerField',
'django.db.models.fields.NullBooleanField': 'rest_framework.serializers.BooleanField',
'django.db.models.fields.PositiveIntegerField': 'rest_framework.serializers.IntegerField',
'django.db.models.fields.PositiveSmallIntegerField': 'rest_framework.serializers.IntegerField',
'django.db.models.fields.SlugField': 'rest_framework.serializers.SlugField',
'django.db.models.fields.SmallIntegerField': 'rest_framework.serializers.IntegerField',
'django.db.models.fields.TextField': 'rest_framework.serializers.CharField',
'django.db.models.fields.TimeField': 'rest_framework.serializers.TimeField',
'django.db.models.fields.URLField': 'rest_framework.serializers.URLField',
'django.db.models.fields.UUIDField': 'rest_framework.serializers.UUIDField',
'django.db.models.fields.JSONField': 'rest_framework.serializers.JSONField',
}

ID_TYPE = 'builtins.object'
11 changes: 5 additions & 6 deletions mypy_drf_plugin/main.py
Expand Up @@ -13,7 +13,7 @@
def transform_serializer_class(ctx: ClassDefContext) -> None:
sym = ctx.api.lookup_fully_qualified_or_none(fullnames.BASE_SERIALIZER_FULLNAME)
if sym is not None and isinstance(sym.node, TypeInfo):
helpers.get_drf_metadata(sym.node)['serializer_bases'][ctx.cls.fullname] = 1
helpers.get_drf_metadata(sym.node)["serializer_bases"][ctx.cls.fullname] = 1

serializers.make_meta_nested_class_inherit_from_any(ctx)

Expand All @@ -28,14 +28,13 @@ def __init__(self, options: Options) -> None:
def _get_currently_defined_serializers(self) -> Dict[str, int]:
base_serializer_sym = self.lookup_fully_qualified(fullnames.BASE_SERIALIZER_FULLNAME)
if base_serializer_sym is not None and isinstance(base_serializer_sym.node, TypeInfo):
return (base_serializer_sym.node.metadata
.setdefault('drf', {})
.setdefault('serializer_bases', {fullnames.BASE_SERIALIZER_FULLNAME: 1}))
return base_serializer_sym.node.metadata.setdefault("drf", {}).setdefault(
"serializer_bases", {fullnames.BASE_SERIALIZER_FULLNAME: 1}
)
else:
return {}

def get_base_class_hook(self, fullname: str
) -> Optional[Callable[[ClassDefContext], None]]:
def get_base_class_hook(self, fullname: str) -> Optional[Callable[[ClassDefContext], None]]:
if fullname in self._get_currently_defined_serializers():
return transform_serializer_class
return None
Expand Down
9 changes: 6 additions & 3 deletions rest_framework-stubs/__init__.pyi
@@ -1,4 +1,7 @@
HTTP_HEADER_ENCODING: str = ...
ISO_8601: str = ...
VERSION: str
HTTP_HEADER_ENCODING: str
ISO_8601: str
default_app_config: str

default_app_config: str = ...
class RemovedInDRF313Warning(DeprecationWarning): ...
class RemovedInDRF314Warning(PendingDeprecationWarning): ...
2 changes: 1 addition & 1 deletion rest_framework-stubs/authentication.pyi
Expand Up @@ -9,7 +9,7 @@ def get_authorization_header(request: Request) -> bytes: ...

class CSRFCheck(CsrfViewMiddleware): ...

class BaseAuthentication(object):
class BaseAuthentication:
def authenticate(self, request: Request) -> Optional[Tuple[Any, Any]]: ...
def authenticate_header(self, request: Request) -> Optional[str]: ...

Expand Down
Empty file.
Empty file.
@@ -0,0 +1,12 @@
from typing import Any

from django.contrib.auth.base_user import AbstractBaseUser
from django.core.management.base import BaseCommand

UserModel: AbstractBaseUser

class Command(BaseCommand):
help: str = ...
def create_user_token(self, username: str, reset_token: bool): ...
def add_arguments(self, parser: Any) -> None: ...
def handle(self, *args: Any, **options: Any) -> None: ...
19 changes: 11 additions & 8 deletions rest_framework-stubs/authtoken/models.pyi
@@ -1,11 +1,14 @@
from django.conf import settings
from typing import Any

from django.db import models
from django.utils.translation import ugettext_lazy as _

class Token(models.Model):
key = models.CharField(_("Key"), max_length=40, primary_key=True)
user = models.OneToOneField(
settings.AUTH_USER_MODEL, related_name="auth_token", on_delete=models.CASCADE, verbose_name=_("User")
)
created = models.DateTimeField(_("Created"), auto_now_add=True)
def generate_key(self) -> str: ...
key: models.CharField = ...
user: models.OneToOneField = ...
created: models.DateTimeField = ...
@classmethod
def generate_key(cls) -> str: ...

class TokenProxy(Token):
@property
def pk(self) -> Any: ...
6 changes: 3 additions & 3 deletions rest_framework-stubs/authtoken/serializers.pyi
@@ -1,6 +1,6 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers

class AuthTokenSerializer(serializers.Serializer):
username = serializers.CharField(label=_("Username"))
password = serializers.CharField(label=_("Password"), style={"input_type": "password"}, trim_whitespace=False)
username: serializers.CharField = ...
password: serializers.CharField = ...
token: serializers.CharField = ...
6 changes: 3 additions & 3 deletions rest_framework-stubs/authtoken/views.pyi
@@ -1,12 +1,12 @@
from typing import Any, Callable
from typing import Any

from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.views import APIView, AsView

class ObtainAuthToken(APIView):
serializer_class = AuthTokenSerializer
def post(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

obtain_auth_token: Callable = ...
obtain_auth_token: AsView = ...
3 changes: 3 additions & 0 deletions rest_framework-stubs/checks.pyi
@@ -0,0 +1,3 @@
from typing import Any, List

def pagination_system_check(app_configs: Any, **kwargs: Any) -> List[Any]: ...
54 changes: 54 additions & 0 deletions rest_framework-stubs/compat.pyi
@@ -0,0 +1,54 @@
from typing import Any, Optional, Tuple, Union

import coreapi # noqa: F401
import requests # noqa: F401
from django.db.models import QuerySet

try:
from django.contrib.postgres import fields as postgres_fields
except ImportError:
postgres_fields = None # type: ignore
try:
import uritemplate
except ImportError:
uritemplate = None # type: ignore
try:
import coreschema
except ImportError:
coreschema = None # type: ignore
try:
import yaml
except ImportError:
yaml = None # type: ignore
try:
import requests
except ImportError:
requests = None # type: ignore
try:
import pygments
except ImportError:
pygments = None # type: ignore
try:
import markdown
def apply_markdown(text: str): ...

except ImportError:
apply_markdown = None # type: ignore
markdown = None # type: ignore

if markdown is not None and pygments is not None:
from markdown.preprocessors import Preprocessor
class CodeBlockPreprocessor(Preprocessor):
pattern: Any = ...
formatter: Any = ...
def run(self, lines: Any): ...

def pygments_css(style: Any) -> Optional[str]: ...
def pygments_highlight(text: str, lang: str, style: Any) -> Any: ...
def md_filter_add_syntax_highlight(md: Any) -> bool: ...
def unicode_http_header(value: Union[str, bytes]) -> str: ...
def distinct(queryset: QuerySet, base: Optional[QuerySet]) -> QuerySet: ...

SHORT_SEPARATORS: Tuple[str, str]
LONG_SEPARATORS: Tuple[str, str]
INDENT_SEPARATORS: Tuple[str, str]
88 changes: 67 additions & 21 deletions rest_framework-stubs/decorators.pyi
@@ -1,29 +1,13 @@
from typing import Any, Callable, Optional, Sequence, Type, TypeVar, Union
from typing import Any, Callable, List, Mapping, Optional, Sequence, Type, Union

from django.http.response import HttpResponseBase
from rest_framework.authentication import BaseAuthentication
from rest_framework.parsers import BaseParser
from rest_framework.permissions import BasePermission
from rest_framework.renderers import BaseRenderer
from rest_framework.schemas.inspectors import ViewInspector
from rest_framework.throttling import BaseThrottle

_VIEW = TypeVar("_VIEW", bound=Callable[..., HttpResponseBase])

def api_view(http_method_names: Optional[Sequence[str]] = ...) -> Callable[[_VIEW], _VIEW]: ...
def renderer_classes(renderer_classes: Sequence[Type[BaseRenderer]]) -> Callable[[_VIEW], _VIEW]: ...
def parser_classes(parser_classes: Sequence[Type[BaseParser]]) -> Callable[[_VIEW], _VIEW]: ...
def authentication_classes(authentication_classes: Sequence[Type[BaseAuthentication]]) -> Callable[[_VIEW], _VIEW]: ...
def throttle_classes(throttle_classes: Sequence[Type[BaseThrottle]]) -> Callable[[_VIEW], _VIEW]: ...
def permission_classes(permission_classes: Sequence[Type[BasePermission]]) -> Callable[[_VIEW], _VIEW]: ...
def schema(view_inspector: Optional[Type[ViewInspector]]) -> Callable[[_VIEW], _VIEW]: ...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You undid the work that I did in #60, this MR seems reckless.

The result is that argument and return types of views are erased if they use any decorator here.

def action(
methods: Optional[Sequence[str]] = ...,
detail: Optional[bool] = ...,
url_path: Optional[str] = ...,
url_name: Optional[str] = ...,
**kwargs: Any
) -> Callable[[_VIEW], _VIEW]: ...
from rest_framework.views import APIView, AsView # noqa: F401
from typing_extensions import Literal

class MethodMapper(dict):
def __init__(self, action: Callable, methods: Sequence[str]) -> None: ...
Expand All @@ -37,5 +21,67 @@ class MethodMapper(dict):
def options(self, func: Callable) -> Callable: ...
def trace(self, func: Callable) -> Callable: ...

def detail_route(methods: Optional[Sequence[str]] = ..., **kwargs: Any) -> Callable[[_VIEW], _VIEW]: ...
def list_route(methods: Optional[Sequence[str]] = ..., **kwargs: Any) -> Callable[[_VIEW], _VIEW]: ...
_LOWER_CASE_HTTP_VERBS = List[
Union[
Literal["get"],
Literal["post"],
Literal["delete"],
Literal["put"],
Literal["patch"],
Literal["trace"],
Literal["options"],
]
]

_MIXED_CASE_HTTP_VERBS = List[
Union[
Literal["GET"],
Literal["POST"],
Literal["DELETE"],
Literal["PUT"],
Literal["PATCH"],
Literal["TRACE"],
Literal["OPTIONS"],
Literal["get"],
Literal["post"],
Literal["delete"],
Literal["put"],
Literal["patch"],
Literal["trace"],
Literal["options"],
]
]

class ViewSetAction:
detail: bool
methods: _LOWER_CASE_HTTP_VERBS
url_path: str
url_name: str
kwargs: Mapping[str, Any]
mapping: MethodMapper
def __call__(self, *args, **kwargs): ...

def api_view(http_method_names: Optional[Sequence[str]] = ...) -> Callable[[Callable], AsView]: ...
def renderer_classes(
renderer_classes: Sequence[Union[BaseRenderer, Type[BaseRenderer]]]
) -> Callable[[Callable], Callable]: ...
def parser_classes(parser_classes: Sequence[Union[BaseParser, Type[BaseParser]]]) -> Callable[[Callable], Callable]: ...
def authentication_classes(
authentication_classes: Sequence[Union[BaseAuthentication, Type[BaseAuthentication]]]
) -> Callable[[Callable], Callable]: ...
def throttle_classes(
throttle_classes: Sequence[Union[BaseThrottle, Type[BaseThrottle]]]
) -> Callable[[Callable], Callable]: ...
def permission_classes(
permission_classes: Sequence[Union[BasePermission, Type[BasePermission]]]
) -> Callable[[Callable], Callable]: ...
def schema(view_inspector: Optional[Union[ViewInspector, Type[ViewInspector]]]) -> Callable[[Callable], Callable]: ...
def action(
methods: Optional[_MIXED_CASE_HTTP_VERBS] = ...,
detail: bool = ...,
url_path: Optional[str] = ...,
url_name: Optional[str] = ...,
suffix: Optional[str] = ...,
name: Optional[str] = ...,
**kwargs: Any,
) -> Callable[[Callable], ViewSetAction]: ...