Skip to content

Commit f5cfca0

Browse files
committed
Add type hints using mypy
1 parent ca44a06 commit f5cfca0

File tree

9 files changed

+149
-99
lines changed

9 files changed

+149
-99
lines changed

inertia/helpers.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
1-
def deep_transform_callables(prop):
1+
from typing import Any
2+
3+
4+
def deep_transform_callables(prop: Any) -> Any:
25
if not isinstance(prop, dict):
36
return prop() if callable(prop) else prop
47

58
for key in list(prop.keys()):
69
prop[key] = deep_transform_callables(prop[key])
710

811
return prop
9-
10-
11-
def validate_type(value, name, expected_type):
12-
if not isinstance(value, expected_type):
13-
raise TypeError(
14-
f"Expected {expected_type.__name__} for {name}, got {type(value).__name__}"
15-
)
16-
17-
return value

inertia/http.py

Lines changed: 77 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from functools import wraps
22
from http import HTTPStatus
33
from json import dumps as json_encode
4+
from typing import Any, Callable
45

56
from django.core.exceptions import ImproperlyConfigured
67
from django.http import HttpRequest, HttpResponse
78
from django.template.loader import render_to_string
89

9-
from .helpers import deep_transform_callables, validate_type
10+
from .helpers import deep_transform_callables
1011
from .prop_classes import DeferredProp, IgnoreOnFirstLoadProp, MergeableProp
1112
from .settings import settings
1213

@@ -15,7 +16,7 @@
1516
# a mock module
1617
import requests
1718
except ImportError:
18-
requests = None
19+
requests = None # type: ignore[assignment]
1920

2021

2122
INERTIA_REQUEST_ENCRYPT_HISTORY = "_inertia_encrypt_history"
@@ -26,51 +27,55 @@
2627

2728

2829
class InertiaRequest(HttpRequest):
29-
def __init__(self, request):
30+
def __init__(self, request: HttpRequest):
3031
super().__init__()
3132
self.__dict__.update(request.__dict__)
3233

3334
@property
34-
def inertia(self):
35+
def inertia(self) -> dict[str, Any]:
3536
inertia_attr = self.__dict__.get("inertia")
3637
return (
3738
inertia_attr.all() if inertia_attr and hasattr(inertia_attr, "all") else {}
3839
)
3940

40-
def is_a_partial_render(self, component):
41+
def is_a_partial_render(self, component: str) -> bool:
4142
return (
4243
"X-Inertia-Partial-Data" in self.headers
4344
and self.headers.get("X-Inertia-Partial-Component", "") == component
4445
)
4546

46-
def partial_keys(self):
47+
def partial_keys(self) -> list[str]:
4748
return self.headers.get("X-Inertia-Partial-Data", "").split(",")
4849

49-
def reset_keys(self):
50+
def reset_keys(self) -> list[str]:
5051
return self.headers.get("X-Inertia-Reset", "").split(",")
5152

52-
def is_inertia(self):
53+
def is_inertia(self) -> bool:
5354
return "X-Inertia" in self.headers
5455

55-
def should_encrypt_history(self):
56-
return validate_type(
57-
getattr(
58-
self,
59-
INERTIA_REQUEST_ENCRYPT_HISTORY,
60-
settings.INERTIA_ENCRYPT_HISTORY,
61-
),
62-
expected_type=bool,
63-
name="encrypt_history",
56+
def should_encrypt_history(self) -> bool:
57+
should_encrypt = getattr(
58+
self, INERTIA_REQUEST_ENCRYPT_HISTORY, settings.INERTIA_ENCRYPT_HISTORY
6459
)
60+
if not isinstance(should_encrypt, bool):
61+
raise TypeError(
62+
f"Expected bool for encrypt_history, got {type(should_encrypt).__name__}"
63+
)
64+
return should_encrypt
6565

6666

6767
class BaseInertiaResponseMixin:
68-
def page_data(self):
69-
clear_history = validate_type(
70-
self.request.session.pop(INERTIA_SESSION_CLEAR_HISTORY, False),
71-
expected_type=bool,
72-
name="clear_history",
73-
)
68+
request: InertiaRequest
69+
component: str
70+
props: dict[str, Any]
71+
template_data: dict[str, Any]
72+
73+
def page_data(self) -> dict[str, Any]:
74+
clear_history = self.request.session.pop(INERTIA_SESSION_CLEAR_HISTORY, False)
75+
if not isinstance(clear_history, bool):
76+
raise TypeError(
77+
f"Expected bool for clear_history, got {type(clear_history).__name__}"
78+
)
7479

7580
_page = {
7681
"component": self.component,
@@ -91,7 +96,7 @@ def page_data(self):
9196

9297
return _page
9398

94-
def build_props(self):
99+
def build_props(self) -> Any:
95100
_props = {
96101
**(self.request.inertia),
97102
**self.props,
@@ -107,18 +112,18 @@ def build_props(self):
107112

108113
return deep_transform_callables(_props)
109114

110-
def build_deferred_props(self):
115+
def build_deferred_props(self) -> dict[str, Any] | None:
111116
if self.request.is_a_partial_render(self.component):
112117
return None
113118

114-
_deferred_props = {}
119+
_deferred_props: dict[str, Any] = {}
115120
for key, prop in self.props.items():
116121
if isinstance(prop, DeferredProp):
117122
_deferred_props.setdefault(prop.group, []).append(key)
118123

119124
return _deferred_props
120125

121-
def build_merge_props(self):
126+
def build_merge_props(self) -> list[str]:
122127
return [
123128
key
124129
for key, prop in self.props.items()
@@ -129,7 +134,7 @@ def build_merge_props(self):
129134
)
130135
]
131136

132-
def build_first_load(self, data):
137+
def build_first_load(self, data: Any) -> str:
133138
context, template = self.build_first_load_context_and_template(data)
134139

135140
try:
@@ -151,7 +156,9 @@ def build_first_load(self, data):
151156
using=None,
152157
)
153158

154-
def build_first_load_context_and_template(self, data):
159+
def build_first_load_context_and_template(
160+
self, data: Any
161+
) -> tuple[dict[str, Any], str]:
155162
if settings.INERTIA_SSR_ENABLED:
156163
try:
157164
response = requests.post(
@@ -178,14 +185,14 @@ class InertiaResponse(BaseInertiaResponseMixin, HttpResponse):
178185

179186
def __init__(
180187
self,
181-
request,
182-
component,
183-
props=None,
184-
template_data=None,
185-
headers=None,
186-
*args,
187-
**kwargs,
188-
):
188+
request: HttpRequest,
189+
component: str,
190+
props: dict[str, Any] | None = None,
191+
template_data: dict[str, Any] | None = None,
192+
headers: dict[str, Any] | None = None,
193+
*args: Any,
194+
**kwargs: Any,
195+
) -> None:
189196
self.request = InertiaRequest(request)
190197
self.component = component
191198
self.props = props or {}
@@ -208,19 +215,30 @@ def __init__(
208215
else:
209216
content = self.build_first_load(data)
210217

211-
super().__init__(
212-
*args,
213-
content=content,
214-
headers=_headers,
215-
**kwargs,
216-
)
218+
if args:
219+
super().__init__(
220+
*args,
221+
headers=_headers,
222+
**kwargs,
223+
)
224+
else:
225+
super().__init__(
226+
content=content,
227+
headers=_headers,
228+
**kwargs,
229+
)
217230

218231

219-
def render(request, component, props=None, template_data=None):
232+
def render(
233+
request: HttpRequest,
234+
component: str,
235+
props: dict[str, Any] | None = None,
236+
template_data: dict[str, Any] | None = None,
237+
) -> InertiaResponse:
220238
return InertiaResponse(request, component, props or {}, template_data or {})
221239

222240

223-
def location(location):
241+
def location(location: str) -> HttpResponse:
224242
return HttpResponse(
225243
"",
226244
status=HTTPStatus.CONFLICT,
@@ -230,18 +248,27 @@ def location(location):
230248
)
231249

232250

233-
def encrypt_history(request, value=True):
251+
def encrypt_history(request: HttpRequest, value: bool = True) -> None:
234252
setattr(request, INERTIA_REQUEST_ENCRYPT_HISTORY, value)
235253

236254

237-
def clear_history(request):
255+
def clear_history(request: HttpRequest) -> None:
238256
request.session[INERTIA_SESSION_CLEAR_HISTORY] = True
239257

240258

241-
def inertia(component):
242-
def decorator(func):
259+
def inertia(
260+
component: str,
261+
) -> Callable[
262+
[Callable[..., HttpResponse | InertiaResponse | dict[str, Any]]],
263+
Callable[..., HttpResponse],
264+
]:
265+
def decorator(
266+
func: Callable[..., HttpResponse | InertiaResponse | dict[str, Any]],
267+
) -> Callable[..., HttpResponse]:
243268
@wraps(func)
244-
def process_inertia_response(request, *args, **kwargs):
269+
def process_inertia_response(
270+
request: HttpRequest, *args: Any, **kwargs: Any
271+
) -> HttpResponse:
245272
props = func(request, *args, **kwargs)
246273

247274
# if a response is returned, return it

inertia/middleware.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
from typing import Callable
2+
13
from django.contrib import messages
4+
from django.http import HttpRequest, HttpResponse
25
from django.middleware.csrf import get_token
36

47
from .http import location
58
from .settings import settings
69

710

811
class InertiaMiddleware:
9-
def __init__(self, get_response):
12+
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]) -> None:
1013
self.get_response = get_response
1114

12-
def __call__(self, request):
15+
def __call__(self, request: HttpRequest) -> HttpResponse:
1316
response = self.get_response(request)
1417

1518
# Inertia requests don't ever render templates, so they skip the typical Django
@@ -27,28 +30,33 @@ def __call__(self, request):
2730

2831
return response
2932

30-
def is_non_post_redirect(self, request, response):
33+
def is_non_post_redirect(
34+
self, request: HttpRequest, response: HttpResponse
35+
) -> bool:
3136
return self.is_redirect_request(response) and request.method in [
3237
"PUT",
3338
"PATCH",
3439
"DELETE",
3540
]
3641

37-
def is_inertia_request(self, request):
42+
def is_inertia_request(self, request: HttpRequest) -> bool:
3843
return "X-Inertia" in request.headers
3944

40-
def is_redirect_request(self, response):
45+
def is_redirect_request(self, response: HttpResponse) -> bool:
4146
return response.status_code in [301, 302]
4247

43-
def is_stale(self, request):
48+
def is_stale(self, request: HttpRequest) -> bool:
4449
return (
4550
request.headers.get("X-Inertia-Version", settings.INERTIA_VERSION)
4651
!= settings.INERTIA_VERSION
4752
)
4853

49-
def is_stale_inertia_get(self, request):
54+
def is_stale_inertia_get(self, request: HttpRequest) -> bool:
5055
return request.method == "GET" and self.is_stale(request)
5156

52-
def force_refresh(self, request):
53-
messages.get_messages(request).used = False
57+
def force_refresh(self, request: HttpRequest) -> HttpResponse:
58+
# If the storage middleware is not defined, get_messages returns an empty list
59+
storage = messages.get_messages(request)
60+
if not isinstance(storage, list):
61+
storage.used = False
5462
return location(request.build_absolute_uri())

inertia/prop_classes.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
from abc import ABC, abstractmethod
2+
from typing import Any
23

34

45
class CallableProp:
5-
def __init__(self, prop):
6+
def __init__(self, prop: Any) -> None:
67
self.prop = prop
78

8-
def __call__(self):
9+
def __call__(self) -> Any:
910
return self.prop() if callable(self.prop) else self.prop
1011

1112

1213
class MergeableProp(ABC):
1314
@abstractmethod
14-
def should_merge(self):
15+
def should_merge(self) -> bool:
1516
pass
1617

1718

@@ -24,15 +25,15 @@ class OptionalProp(CallableProp, IgnoreOnFirstLoadProp):
2425

2526

2627
class DeferredProp(CallableProp, MergeableProp, IgnoreOnFirstLoadProp):
27-
def __init__(self, prop, group, merge=False):
28+
def __init__(self, prop: Any, group: str, merge: bool = False) -> None:
2829
super().__init__(prop)
2930
self.group = group
3031
self.merge = merge
3132

32-
def should_merge(self):
33+
def should_merge(self) -> bool:
3334
return self.merge
3435

3536

3637
class MergeProp(CallableProp, MergeableProp):
37-
def should_merge(self):
38+
def should_merge(self) -> bool:
3839
return True

inertia/settings.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Any
2+
13
from django.conf import settings as django_settings
24

35
from .utils import InertiaJsonEncoder
@@ -12,7 +14,7 @@ class InertiaSettings:
1214
INERTIA_SSR_ENABLED = False
1315
INERTIA_ENCRYPT_HISTORY = False
1416

15-
def __getattribute__(self, name):
17+
def __getattribute__(self, name: str) -> Any:
1618
try:
1719
return getattr(django_settings, name)
1820
except AttributeError:

0 commit comments

Comments
 (0)