Skip to content

Commit c24f046

Browse files
authored
Merge pull request #414 from p1c2u/feature/mypy-static-types
Static types with mypy
2 parents 81ab62a + e7c8b6f commit c24f046

Some content is hidden

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

85 files changed

+1251
-585
lines changed

Diff for: .github/workflows/python-test.yml

+3
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ jobs:
5555
PYTEST_ADDOPTS: "--color=yes"
5656
run: poetry run pytest
5757

58+
- name: Static type check
59+
run: poetry run mypy
60+
5861
- name: Upload coverage
5962
uses: codecov/codecov-action@v1
6063

Diff for: openapi_core/casting/schemas/casters.py

+19-9
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,59 @@
1+
from typing import TYPE_CHECKING
2+
from typing import Any
3+
from typing import Callable
4+
from typing import List
5+
6+
from openapi_core.casting.schemas.datatypes import CasterCallable
17
from openapi_core.casting.schemas.exceptions import CastError
8+
from openapi_core.spec import Spec
9+
10+
if TYPE_CHECKING:
11+
from openapi_core.casting.schemas.factories import SchemaCastersFactory
212

313

414
class BaseSchemaCaster:
5-
def __init__(self, schema):
15+
def __init__(self, schema: Spec):
616
self.schema = schema
717

8-
def __call__(self, value):
18+
def __call__(self, value: Any) -> Any:
919
if value is None:
1020
return value
1121

1222
return self.cast(value)
1323

14-
def cast(self, value):
24+
def cast(self, value: Any) -> Any:
1525
raise NotImplementedError
1626

1727

1828
class CallableSchemaCaster(BaseSchemaCaster):
19-
def __init__(self, schema, caster_callable):
29+
def __init__(self, schema: Spec, caster_callable: CasterCallable):
2030
super().__init__(schema)
2131
self.caster_callable = caster_callable
2232

23-
def cast(self, value):
33+
def cast(self, value: Any) -> Any:
2434
try:
2535
return self.caster_callable(value)
2636
except (ValueError, TypeError):
2737
raise CastError(value, self.schema["type"])
2838

2939

3040
class DummyCaster(BaseSchemaCaster):
31-
def cast(self, value):
41+
def cast(self, value: Any) -> Any:
3242
return value
3343

3444

3545
class ComplexCaster(BaseSchemaCaster):
36-
def __init__(self, schema, casters_factory):
46+
def __init__(self, schema: Spec, casters_factory: "SchemaCastersFactory"):
3747
super().__init__(schema)
3848
self.casters_factory = casters_factory
3949

4050

4151
class ArrayCaster(ComplexCaster):
4252
@property
43-
def items_caster(self):
53+
def items_caster(self) -> BaseSchemaCaster:
4454
return self.casters_factory.create(self.schema / "items")
4555

46-
def cast(self, value):
56+
def cast(self, value: Any) -> List[Any]:
4757
try:
4858
return list(map(self.items_caster, value))
4959
except (ValueError, TypeError):

Diff for: openapi_core/casting/schemas/datatypes.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from typing import Any
2+
from typing import Callable
3+
4+
CasterCallable = Callable[[Any], Any]

Diff for: openapi_core/casting/schemas/exceptions.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ class CastError(OpenAPIError):
1010
value: str
1111
type: str
1212

13-
def __str__(self):
13+
def __str__(self) -> str:
1414
return f"Failed to cast value to {self.type} type: {self.value}"

Diff for: openapi_core/casting/schemas/factories.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
from typing import Dict
2+
13
from openapi_core.casting.schemas.casters import ArrayCaster
4+
from openapi_core.casting.schemas.casters import BaseSchemaCaster
25
from openapi_core.casting.schemas.casters import CallableSchemaCaster
36
from openapi_core.casting.schemas.casters import DummyCaster
7+
from openapi_core.casting.schemas.datatypes import CasterCallable
8+
from openapi_core.spec import Spec
49
from openapi_core.util import forcebool
510

611

@@ -11,7 +16,7 @@ class SchemaCastersFactory:
1116
"object",
1217
"any",
1318
]
14-
PRIMITIVE_CASTERS = {
19+
PRIMITIVE_CASTERS: Dict[str, CasterCallable] = {
1520
"integer": int,
1621
"number": float,
1722
"boolean": forcebool,
@@ -20,7 +25,7 @@ class SchemaCastersFactory:
2025
"array": ArrayCaster,
2126
}
2227

23-
def create(self, schema):
28+
def create(self, schema: Spec) -> BaseSchemaCaster:
2429
schema_type = schema.getkey("type", "any")
2530

2631
if schema_type in self.DUMMY_CASTERS:

Diff for: openapi_core/contrib/django/handlers.py

+18-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
"""OpenAPI core contrib django handlers module"""
2+
from typing import Any
3+
from typing import Dict
4+
from typing import Iterable
5+
from typing import Optional
6+
from typing import Type
7+
28
from django.http import JsonResponse
9+
from django.http.request import HttpRequest
10+
from django.http.response import HttpResponse
311

412
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
513
from openapi_core.templating.paths.exceptions import OperationNotFound
@@ -11,7 +19,7 @@
1119

1220
class DjangoOpenAPIErrorsHandler:
1321

14-
OPENAPI_ERROR_STATUS = {
22+
OPENAPI_ERROR_STATUS: Dict[Type[Exception], int] = {
1523
MissingRequiredParameter: 400,
1624
ServerNotFound: 400,
1725
InvalidSecurity: 403,
@@ -21,7 +29,12 @@ class DjangoOpenAPIErrorsHandler:
2129
}
2230

2331
@classmethod
24-
def handle(cls, errors, req, resp=None):
32+
def handle(
33+
cls,
34+
errors: Iterable[Exception],
35+
req: HttpRequest,
36+
resp: Optional[HttpResponse] = None,
37+
) -> JsonResponse:
2538
data_errors = [cls.format_openapi_error(err) for err in errors]
2639
data = {
2740
"errors": data_errors,
@@ -30,13 +43,13 @@ def handle(cls, errors, req, resp=None):
3043
return JsonResponse(data, status=data_error_max["status"])
3144

3245
@classmethod
33-
def format_openapi_error(cls, error):
46+
def format_openapi_error(cls, error: Exception) -> Dict[str, Any]:
3447
return {
3548
"title": str(error),
3649
"status": cls.OPENAPI_ERROR_STATUS.get(error.__class__, 400),
3750
"class": str(type(error)),
3851
}
3952

4053
@classmethod
41-
def get_error_status(cls, error):
42-
return error["status"]
54+
def get_error_status(cls, error: Dict[str, Any]) -> str:
55+
return str(error["status"])

Diff for: openapi_core/contrib/django/middlewares.py

+26-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
"""OpenAPI core contrib django middlewares module"""
2+
from typing import Callable
3+
24
from django.conf import settings
35
from django.core.exceptions import ImproperlyConfigured
6+
from django.http import JsonResponse
7+
from django.http.request import HttpRequest
8+
from django.http.response import HttpResponse
49

510
from openapi_core.contrib.django.handlers import DjangoOpenAPIErrorsHandler
611
from openapi_core.contrib.django.requests import DjangoOpenAPIRequest
712
from openapi_core.contrib.django.responses import DjangoOpenAPIResponse
813
from openapi_core.validation.processors import OpenAPIProcessor
914
from openapi_core.validation.request import openapi_request_validator
15+
from openapi_core.validation.request.datatypes import RequestValidationResult
16+
from openapi_core.validation.request.protocols import Request
1017
from openapi_core.validation.response import openapi_response_validator
18+
from openapi_core.validation.response.datatypes import ResponseValidationResult
19+
from openapi_core.validation.response.protocols import Response
1120

1221

1322
class DjangoOpenAPIMiddleware:
@@ -16,7 +25,7 @@ class DjangoOpenAPIMiddleware:
1625
response_class = DjangoOpenAPIResponse
1726
errors_handler = DjangoOpenAPIErrorsHandler()
1827

19-
def __init__(self, get_response):
28+
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
2029
self.get_response = get_response
2130

2231
if not hasattr(settings, "OPENAPI_SPEC"):
@@ -26,7 +35,7 @@ def __init__(self, get_response):
2635
openapi_request_validator, openapi_response_validator
2736
)
2837

29-
def __call__(self, request):
38+
def __call__(self, request: HttpRequest) -> HttpResponse:
3039
openapi_request = self._get_openapi_request(request)
3140
req_result = self.validation_processor.process_request(
3241
settings.OPENAPI_SPEC, openapi_request
@@ -46,14 +55,25 @@ def __call__(self, request):
4655

4756
return response
4857

49-
def _handle_request_errors(self, request_result, req):
58+
def _handle_request_errors(
59+
self, request_result: RequestValidationResult, req: HttpRequest
60+
) -> JsonResponse:
5061
return self.errors_handler.handle(request_result.errors, req, None)
5162

52-
def _handle_response_errors(self, response_result, req, resp):
63+
def _handle_response_errors(
64+
self,
65+
response_result: ResponseValidationResult,
66+
req: HttpRequest,
67+
resp: HttpResponse,
68+
) -> JsonResponse:
5369
return self.errors_handler.handle(response_result.errors, req, resp)
5470

55-
def _get_openapi_request(self, request):
71+
def _get_openapi_request(
72+
self, request: HttpRequest
73+
) -> DjangoOpenAPIRequest:
5674
return self.request_class(request)
5775

58-
def _get_openapi_response(self, response):
76+
def _get_openapi_response(
77+
self, response: HttpResponse
78+
) -> DjangoOpenAPIResponse:
5979
return self.response_class(response)

Diff for: openapi_core/contrib/django/requests.py

+23-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""OpenAPI core contrib django requests module"""
22
import re
3-
from urllib.parse import urljoin
3+
from typing import Optional
44

5+
from django.http.request import HttpRequest
56
from werkzeug.datastructures import Headers
67
from werkzeug.datastructures import ImmutableMultiDict
78

@@ -24,28 +25,33 @@ class DjangoOpenAPIRequest:
2425

2526
path_regex = re.compile(PATH_PARAMETER_PATTERN)
2627

27-
def __init__(self, request):
28+
def __init__(self, request: HttpRequest):
2829
self.request = request
2930

30-
self.parameters = RequestParameters(
31-
path=self.request.resolver_match
31+
path = (
32+
self.request.resolver_match
3233
and self.request.resolver_match.kwargs
33-
or {},
34+
or {}
35+
)
36+
self.parameters = RequestParameters(
37+
path=path,
3438
query=ImmutableMultiDict(self.request.GET),
3539
header=Headers(self.request.headers.items()),
3640
cookie=ImmutableMultiDict(dict(self.request.COOKIES)),
3741
)
3842

3943
@property
40-
def host_url(self):
44+
def host_url(self) -> str:
45+
assert isinstance(self.request._current_scheme_host, str)
4146
return self.request._current_scheme_host
4247

4348
@property
44-
def path(self):
49+
def path(self) -> str:
50+
assert isinstance(self.request.path, str)
4551
return self.request.path
4652

4753
@property
48-
def path_pattern(self):
54+
def path_pattern(self) -> Optional[str]:
4955
if self.request.resolver_match is None:
5056
return None
5157

@@ -58,13 +64,17 @@ def path_pattern(self):
5864
return "/" + route
5965

6066
@property
61-
def method(self):
67+
def method(self) -> str:
68+
if self.request.method is None:
69+
return ""
70+
assert isinstance(self.request.method, str)
6271
return self.request.method.lower()
6372

6473
@property
65-
def body(self):
66-
return self.request.body
74+
def body(self) -> str:
75+
assert isinstance(self.request.body, bytes)
76+
return self.request.body.decode("utf-8")
6777

6878
@property
69-
def mimetype(self):
70-
return self.request.content_type
79+
def mimetype(self) -> str:
80+
return self.request.content_type or ""

Diff for: openapi_core/contrib/django/responses.py

+12-7
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
"""OpenAPI core contrib django responses module"""
2+
from django.http.response import HttpResponse
23
from werkzeug.datastructures import Headers
34

45

56
class DjangoOpenAPIResponse:
6-
def __init__(self, response):
7+
def __init__(self, response: HttpResponse):
78
self.response = response
89

910
@property
10-
def data(self):
11-
return self.response.content
11+
def data(self) -> str:
12+
assert isinstance(self.response.content, bytes)
13+
return self.response.content.decode("utf-8")
1214

1315
@property
14-
def status_code(self):
16+
def status_code(self) -> int:
17+
assert isinstance(self.response.status_code, int)
1518
return self.response.status_code
1619

1720
@property
18-
def headers(self):
21+
def headers(self) -> Headers:
1922
return Headers(self.response.headers.items())
2023

2124
@property
22-
def mimetype(self):
23-
return self.response["Content-Type"]
25+
def mimetype(self) -> str:
26+
content_type = self.response.get("Content-Type", "")
27+
assert isinstance(content_type, str)
28+
return content_type

0 commit comments

Comments
 (0)