litestar-vite 0.13.0__py3-none-any.whl → 0.13.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of litestar-vite might be problematic. Click here for more details.
- litestar_vite/__init__.py +0 -2
- litestar_vite/__metadata__.py +0 -2
- litestar_vite/cli.py +33 -38
- litestar_vite/commands.py +45 -35
- litestar_vite/config.py +6 -7
- litestar_vite/inertia/_utils.py +53 -17
- litestar_vite/inertia/config.py +5 -7
- litestar_vite/inertia/exception_handler.py +22 -8
- litestar_vite/inertia/helpers.py +57 -61
- litestar_vite/inertia/middleware.py +4 -6
- litestar_vite/inertia/plugin.py +22 -9
- litestar_vite/inertia/request.py +58 -20
- litestar_vite/inertia/response.py +42 -40
- litestar_vite/inertia/routes.py +7 -9
- litestar_vite/inertia/types.py +6 -8
- litestar_vite/loader.py +93 -29
- litestar_vite/plugin.py +41 -33
- {litestar_vite-0.13.0.dist-info → litestar_vite-0.13.1.dist-info}/METADATA +3 -3
- litestar_vite-0.13.1.dist-info/RECORD +30 -0
- litestar_vite-0.13.0.dist-info/RECORD +0 -30
- {litestar_vite-0.13.0.dist-info → litestar_vite-0.13.1.dist-info}/WHEEL +0 -0
- {litestar_vite-0.13.0.dist-info → litestar_vite-0.13.1.dist-info}/licenses/LICENSE +0 -0
litestar_vite/inertia/helpers.py
CHANGED
|
@@ -1,25 +1,10 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
import inspect
|
|
4
2
|
from collections import defaultdict
|
|
5
|
-
from collections.abc import Mapping
|
|
3
|
+
from collections.abc import Coroutine, Generator, Iterable, Mapping
|
|
6
4
|
from contextlib import contextmanager
|
|
7
5
|
from functools import lru_cache
|
|
8
6
|
from textwrap import dedent
|
|
9
|
-
from typing import
|
|
10
|
-
TYPE_CHECKING,
|
|
11
|
-
Any,
|
|
12
|
-
Callable,
|
|
13
|
-
Coroutine,
|
|
14
|
-
Dict,
|
|
15
|
-
Generator,
|
|
16
|
-
Generic,
|
|
17
|
-
Iterable,
|
|
18
|
-
List,
|
|
19
|
-
TypeVar,
|
|
20
|
-
cast,
|
|
21
|
-
overload,
|
|
22
|
-
)
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Callable, Generic, Optional, TypeVar, Union, cast, overload
|
|
23
8
|
|
|
24
9
|
from anyio.from_thread import BlockingPortal, start_blocking_portal
|
|
25
10
|
from litestar.exceptions import ImproperlyConfiguredException
|
|
@@ -41,36 +26,43 @@ StaticT = TypeVar("StaticT", bound=object)
|
|
|
41
26
|
|
|
42
27
|
|
|
43
28
|
@overload
|
|
44
|
-
def lazy(key: str, value_or_callable: None) -> StaticProp[str, None]: ...
|
|
29
|
+
def lazy(key: str, value_or_callable: "None") -> "StaticProp[str, None]": ...
|
|
45
30
|
|
|
46
31
|
|
|
47
32
|
@overload
|
|
48
|
-
def lazy(key: str, value_or_callable: T) -> StaticProp[str, T]: ...
|
|
33
|
+
def lazy(key: str, value_or_callable: "T") -> "StaticProp[str, T]": ...
|
|
49
34
|
|
|
50
35
|
|
|
51
36
|
@overload
|
|
52
|
-
def lazy(key: str, value_or_callable: Callable[..., None] = ...) -> DeferredProp[str, None]: ...
|
|
37
|
+
def lazy(key: str, value_or_callable: "Callable[..., None]" = ...) -> "DeferredProp[str, None]": ...
|
|
53
38
|
|
|
54
39
|
|
|
55
40
|
@overload
|
|
56
|
-
def lazy(
|
|
41
|
+
def lazy(
|
|
42
|
+
key: str, value_or_callable: "Callable[..., Coroutine[Any, Any, None]]" = ...
|
|
43
|
+
) -> "DeferredProp[str, None]": ...
|
|
57
44
|
|
|
58
45
|
|
|
59
46
|
@overload
|
|
60
47
|
def lazy(
|
|
61
48
|
key: str,
|
|
62
|
-
value_or_callable: Callable[..., T
|
|
63
|
-
) -> DeferredProp[str, T]: ...
|
|
49
|
+
value_or_callable: "Callable[..., Union[T, Coroutine[Any, Any, T]]]" = ..., # pyright: ignore[reportInvalidTypeVarUse]
|
|
50
|
+
) -> "DeferredProp[str, T]": ...
|
|
64
51
|
|
|
65
52
|
|
|
66
53
|
def lazy(
|
|
67
54
|
key: str,
|
|
68
|
-
value_or_callable: None
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
55
|
+
value_or_callable: "Optional[Union[T, Callable[..., Coroutine[Any, Any, None]], Callable[..., T], Callable[..., Union[T, Coroutine[Any, Any, T]]]]]" = None,
|
|
56
|
+
) -> "Union[StaticProp[str, None], StaticProp[str, T], DeferredProp[str, T], DeferredProp[str, None]]":
|
|
57
|
+
"""Wrap an async function to return a DeferredProp.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
key: The key to store the value under.
|
|
61
|
+
value_or_callable: The value or callable to store.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
The wrapped value or callable.
|
|
65
|
+
"""
|
|
74
66
|
if value_or_callable is None:
|
|
75
67
|
return StaticProp[str, None](key=key, value=None)
|
|
76
68
|
|
|
@@ -83,15 +75,15 @@ def lazy(
|
|
|
83
75
|
class StaticProp(Generic[PropKeyT, StaticT]):
|
|
84
76
|
"""A wrapper for static property evaluation."""
|
|
85
77
|
|
|
86
|
-
def __init__(self, key: PropKeyT, value: StaticT) -> None:
|
|
78
|
+
def __init__(self, key: "PropKeyT", value: "StaticT") -> None:
|
|
87
79
|
self._key = key
|
|
88
80
|
self._result = value
|
|
89
81
|
|
|
90
82
|
@property
|
|
91
|
-
def key(self) -> PropKeyT:
|
|
83
|
+
def key(self) -> "PropKeyT":
|
|
92
84
|
return self._key
|
|
93
85
|
|
|
94
|
-
def render(self, portal: BlockingPortal
|
|
86
|
+
def render(self, portal: "Optional[BlockingPortal]" = None) -> "StaticT":
|
|
95
87
|
return self._result
|
|
96
88
|
|
|
97
89
|
|
|
@@ -99,19 +91,20 @@ class DeferredProp(Generic[PropKeyT, T]):
|
|
|
99
91
|
"""A wrapper for deferred property evaluation."""
|
|
100
92
|
|
|
101
93
|
def __init__(
|
|
102
|
-
self, key: PropKeyT, value: Callable[...,
|
|
94
|
+
self, key: "PropKeyT", value: "Optional[Callable[..., Optional[Union[T, Coroutine[Any, Any, T]]]]]" = None
|
|
103
95
|
) -> None:
|
|
104
96
|
self._key = key
|
|
105
97
|
self._value = value
|
|
106
98
|
self._evaluated = False
|
|
107
|
-
self._result: T
|
|
99
|
+
self._result: "Optional[T]" = None
|
|
108
100
|
|
|
109
101
|
@property
|
|
110
|
-
def key(self) -> PropKeyT:
|
|
102
|
+
def key(self) -> "PropKeyT":
|
|
111
103
|
return self._key
|
|
112
104
|
|
|
105
|
+
@staticmethod
|
|
113
106
|
@contextmanager
|
|
114
|
-
def with_portal(
|
|
107
|
+
def with_portal(portal: "Optional[BlockingPortal]" = None) -> "Generator[BlockingPortal, None, None]":
|
|
115
108
|
if portal is None:
|
|
116
109
|
with start_blocking_portal() as p:
|
|
117
110
|
yield p
|
|
@@ -120,11 +113,11 @@ class DeferredProp(Generic[PropKeyT, T]):
|
|
|
120
113
|
|
|
121
114
|
@staticmethod
|
|
122
115
|
def _is_awaitable(
|
|
123
|
-
v: Callable[..., T
|
|
124
|
-
) -> TypeGuard[Coroutine[Any, Any, T]]:
|
|
116
|
+
v: "Callable[..., Union[T, Coroutine[Any, Any, T]]]",
|
|
117
|
+
) -> "TypeGuard[Coroutine[Any, Any, T]]":
|
|
125
118
|
return inspect.iscoroutinefunction(v)
|
|
126
119
|
|
|
127
|
-
def render(self, portal: BlockingPortal
|
|
120
|
+
def render(self, portal: "Optional[BlockingPortal]" = None) -> "Union[T, None]":
|
|
128
121
|
if self._evaluated:
|
|
129
122
|
return self._result
|
|
130
123
|
if self._value is None or not callable(self._value):
|
|
@@ -141,7 +134,7 @@ class DeferredProp(Generic[PropKeyT, T]):
|
|
|
141
134
|
return self._result
|
|
142
135
|
|
|
143
136
|
|
|
144
|
-
def is_lazy_prop(value: Any) -> TypeGuard[DeferredProp[Any, Any]]:
|
|
137
|
+
def is_lazy_prop(value: "Any") -> "TypeGuard[Union[DeferredProp[Any, Any], StaticProp[Any, Any]]]":
|
|
145
138
|
"""Check if value is a deferred property.
|
|
146
139
|
|
|
147
140
|
Args:
|
|
@@ -153,7 +146,7 @@ def is_lazy_prop(value: Any) -> TypeGuard[DeferredProp[Any, Any]]:
|
|
|
153
146
|
return isinstance(value, (DeferredProp, StaticProp))
|
|
154
147
|
|
|
155
148
|
|
|
156
|
-
def should_render(value: Any, partial_data: set[str]
|
|
149
|
+
def should_render(value: "Any", partial_data: "Optional[set[str]]" = None) -> "bool":
|
|
157
150
|
"""Check if value should be rendered.
|
|
158
151
|
|
|
159
152
|
Args:
|
|
@@ -169,7 +162,7 @@ def should_render(value: Any, partial_data: set[str] | None = None) -> bool:
|
|
|
169
162
|
return True
|
|
170
163
|
|
|
171
164
|
|
|
172
|
-
def is_or_contains_lazy_prop(value: Any) -> bool:
|
|
165
|
+
def is_or_contains_lazy_prop(value: "Any") -> "bool":
|
|
173
166
|
"""Check if value is or contains a deferred property.
|
|
174
167
|
|
|
175
168
|
Args:
|
|
@@ -189,13 +182,16 @@ def is_or_contains_lazy_prop(value: Any) -> bool:
|
|
|
189
182
|
return False
|
|
190
183
|
|
|
191
184
|
|
|
192
|
-
def lazy_render(
|
|
185
|
+
def lazy_render(
|
|
186
|
+
value: "T", partial_data: "Optional[set[str]]" = None, portal: "Optional[BlockingPortal]" = None
|
|
187
|
+
) -> "T":
|
|
193
188
|
"""Filter deferred properties from the value based on partial data.
|
|
194
189
|
|
|
195
190
|
Args:
|
|
196
191
|
value: The value to filter
|
|
197
192
|
partial_data: Keys for partial rendering
|
|
198
193
|
portal: Optional portal to use for async rendering
|
|
194
|
+
|
|
199
195
|
Returns:
|
|
200
196
|
The filtered value
|
|
201
197
|
"""
|
|
@@ -225,9 +221,9 @@ def lazy_render(value: T, partial_data: set[str] | None = None, portal: Blocking
|
|
|
225
221
|
|
|
226
222
|
|
|
227
223
|
def get_shared_props(
|
|
228
|
-
request: ASGIConnection[Any, Any, Any, Any],
|
|
229
|
-
partial_data: set[str]
|
|
230
|
-
) -> dict[str, Any]:
|
|
224
|
+
request: "ASGIConnection[Any, Any, Any, Any]",
|
|
225
|
+
partial_data: "Optional[set[str]]" = None,
|
|
226
|
+
) -> "dict[str, Any]":
|
|
231
227
|
"""Return shared session props for a request.
|
|
232
228
|
|
|
233
229
|
Args:
|
|
@@ -240,14 +236,14 @@ def get_shared_props(
|
|
|
240
236
|
Note:
|
|
241
237
|
Be sure to call this before `self.create_template_context` if you would like to include the `flash` message details.
|
|
242
238
|
"""
|
|
243
|
-
props: dict[str, Any] = {}
|
|
244
|
-
flash: dict[str, list[str]] = defaultdict(list)
|
|
245
|
-
errors: dict[str, Any] = {}
|
|
239
|
+
props: "dict[str, Any]" = {}
|
|
240
|
+
flash: "dict[str, list[str]]" = defaultdict(list)
|
|
241
|
+
errors: "dict[str, Any]" = {}
|
|
246
242
|
error_bag = request.headers.get("X-Inertia-Error-Bag", None)
|
|
247
243
|
|
|
248
244
|
try:
|
|
249
245
|
errors = request.session.pop("_errors", {})
|
|
250
|
-
shared_props = cast("
|
|
246
|
+
shared_props = cast("dict[str,Any]", request.session.pop("_shared", {}))
|
|
251
247
|
inertia_plugin = cast("InertiaPlugin", request.app.plugins.get("InertiaPlugin"))
|
|
252
248
|
|
|
253
249
|
# Handle deferred props
|
|
@@ -258,7 +254,7 @@ def get_shared_props(
|
|
|
258
254
|
if should_render(value, partial_data):
|
|
259
255
|
props[key] = value
|
|
260
256
|
|
|
261
|
-
for message in cast("
|
|
257
|
+
for message in cast("list[dict[str,Any]]", request.session.pop("_messages", [])):
|
|
262
258
|
flash[message["category"]].append(message["message"])
|
|
263
259
|
|
|
264
260
|
props.update(inertia_plugin.config.extra_static_page_props)
|
|
@@ -277,10 +273,10 @@ def get_shared_props(
|
|
|
277
273
|
|
|
278
274
|
|
|
279
275
|
def share(
|
|
280
|
-
connection: ASGIConnection[Any, Any, Any, Any],
|
|
281
|
-
key: str,
|
|
282
|
-
value: Any,
|
|
283
|
-
) -> None:
|
|
276
|
+
connection: "ASGIConnection[Any, Any, Any, Any]",
|
|
277
|
+
key: "str",
|
|
278
|
+
value: "Any",
|
|
279
|
+
) -> "None":
|
|
284
280
|
"""Share a value in the session.
|
|
285
281
|
|
|
286
282
|
Args:
|
|
@@ -296,10 +292,10 @@ def share(
|
|
|
296
292
|
|
|
297
293
|
|
|
298
294
|
def error(
|
|
299
|
-
connection: ASGIConnection[Any, Any, Any, Any],
|
|
300
|
-
key: str,
|
|
301
|
-
message: str,
|
|
302
|
-
) -> None:
|
|
295
|
+
connection: "ASGIConnection[Any, Any, Any, Any]",
|
|
296
|
+
key: "str",
|
|
297
|
+
message: "str",
|
|
298
|
+
) -> "None":
|
|
303
299
|
"""Set an error message in the session.
|
|
304
300
|
|
|
305
301
|
Args:
|
|
@@ -314,9 +310,9 @@ def error(
|
|
|
314
310
|
connection.logger.warning(msg)
|
|
315
311
|
|
|
316
312
|
|
|
317
|
-
def js_routes_script(js_routes: Routes) -> Markup:
|
|
313
|
+
def js_routes_script(js_routes: "Routes") -> "Markup":
|
|
318
314
|
@lru_cache
|
|
319
|
-
def _markup_safe_json_dumps(js_routes: str) -> Markup:
|
|
315
|
+
def _markup_safe_json_dumps(js_routes: "str") -> "Markup":
|
|
320
316
|
js = js_routes.replace("<", "\\u003c").replace(">", "\\u003e").replace("&", "\\u0026").replace("'", "\\u0027")
|
|
321
317
|
return Markup(js)
|
|
322
318
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING, Any
|
|
1
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
4
2
|
|
|
5
3
|
from litestar import Request
|
|
6
4
|
from litestar.middleware import AbstractMiddleware
|
|
@@ -18,7 +16,7 @@ if TYPE_CHECKING:
|
|
|
18
16
|
from litestar.types import ASGIApp, Receive, Scope, Send
|
|
19
17
|
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
def redirect_on_asset_version_mismatch(request: "Request[UserT, AuthT, StateT]") -> "Optional[InertiaRedirect]":
|
|
22
20
|
if getattr(request, "is_inertia", None) is None:
|
|
23
21
|
return None
|
|
24
22
|
inertia_version = request.headers.get("X-Inertia-Version")
|
|
@@ -32,7 +30,7 @@ async def redirect_on_asset_version_mismatch(request: Request[UserT, AuthT, Stat
|
|
|
32
30
|
|
|
33
31
|
|
|
34
32
|
class InertiaMiddleware(AbstractMiddleware):
|
|
35
|
-
def __init__(self, app: ASGIApp) -> None:
|
|
33
|
+
def __init__(self, app: "ASGIApp") -> None:
|
|
36
34
|
super().__init__(app)
|
|
37
35
|
self.app = app
|
|
38
36
|
|
|
@@ -43,7 +41,7 @@ class InertiaMiddleware(AbstractMiddleware):
|
|
|
43
41
|
send: "Send",
|
|
44
42
|
) -> None:
|
|
45
43
|
request = Request[Any, Any, Any](scope=scope)
|
|
46
|
-
redirect =
|
|
44
|
+
redirect = redirect_on_asset_version_mismatch(request)
|
|
47
45
|
if redirect is not None:
|
|
48
46
|
response = redirect.to_asgi_response(app=None, request=request) # pyright: ignore[reportUnknownMemberType]
|
|
49
47
|
await response(scope, receive, send)
|
litestar_vite/inertia/plugin.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
from contextlib import asynccontextmanager
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
5
3
|
|
|
6
4
|
from anyio.from_thread import start_blocking_portal
|
|
7
5
|
from litestar.plugins import InitPluginProtocol
|
|
8
6
|
|
|
9
7
|
if TYPE_CHECKING:
|
|
8
|
+
from collections.abc import AsyncGenerator
|
|
9
|
+
|
|
10
10
|
from anyio.from_thread import BlockingPortal
|
|
11
11
|
from litestar import Litestar
|
|
12
12
|
from litestar.config.app import AppConfig
|
|
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
|
|
|
14
14
|
from litestar_vite.inertia.config import InertiaConfig
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def set_js_routes(app: Litestar) -> None:
|
|
17
|
+
def set_js_routes(app: "Litestar") -> "None":
|
|
18
18
|
"""Generate the route structure of the application on startup."""
|
|
19
19
|
from litestar_vite.inertia.routes import generate_js_routes
|
|
20
20
|
|
|
@@ -27,7 +27,7 @@ class InertiaPlugin(InitPluginProtocol):
|
|
|
27
27
|
|
|
28
28
|
__slots__ = ("_portal", "config")
|
|
29
29
|
|
|
30
|
-
def __init__(self, config: InertiaConfig) -> None:
|
|
30
|
+
def __init__(self, config: "InertiaConfig") -> "None":
|
|
31
31
|
"""Initialize ``Inertia``.
|
|
32
32
|
|
|
33
33
|
Args:
|
|
@@ -36,23 +36,36 @@ class InertiaPlugin(InitPluginProtocol):
|
|
|
36
36
|
self.config = config
|
|
37
37
|
|
|
38
38
|
@asynccontextmanager
|
|
39
|
-
async def lifespan(self, app: Litestar) -> AsyncGenerator[None, None]:
|
|
40
|
-
"""Lifespan to ensure the event loop is available.
|
|
39
|
+
async def lifespan(self, app: "Litestar") -> "AsyncGenerator[None, None]":
|
|
40
|
+
"""Lifespan to ensure the event loop is available.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
app: The :class:`Litestar <litestar.app.Litestar>` instance.
|
|
44
|
+
|
|
45
|
+
Yields:
|
|
46
|
+
An asynchronous context manager.
|
|
47
|
+
"""
|
|
41
48
|
|
|
42
49
|
with start_blocking_portal() as portal:
|
|
43
50
|
self._portal = portal
|
|
44
51
|
yield
|
|
45
52
|
|
|
46
53
|
@property
|
|
47
|
-
def portal(self) -> BlockingPortal:
|
|
54
|
+
def portal(self) -> "BlockingPortal":
|
|
48
55
|
"""Get the portal."""
|
|
49
56
|
return self._portal
|
|
50
57
|
|
|
51
|
-
def on_app_init(self, app_config: AppConfig) -> AppConfig:
|
|
58
|
+
def on_app_init(self, app_config: "AppConfig") -> "AppConfig":
|
|
52
59
|
"""Configure application for use with Vite.
|
|
53
60
|
|
|
54
61
|
Args:
|
|
55
62
|
app_config: The :class:`AppConfig <litestar.config.app.AppConfig>` instance.
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ImproperlyConfiguredException: If the Inertia plugin is not properly configured.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
The :class:`AppConfig <litestar.config.app.AppConfig>` instance.
|
|
56
69
|
"""
|
|
57
70
|
|
|
58
71
|
from litestar.exceptions import ImproperlyConfiguredException
|
litestar_vite/inertia/request.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
from functools import cached_property
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
2
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
3
|
from urllib.parse import unquote
|
|
6
4
|
|
|
7
5
|
from litestar import Request
|
|
@@ -25,14 +23,20 @@ if TYPE_CHECKING:
|
|
|
25
23
|
class InertiaDetails:
|
|
26
24
|
"""InertiaDetails holds all the values sent by Inertia client in headers and provide convenient properties."""
|
|
27
25
|
|
|
28
|
-
def __init__(self, request: Request[UserT, AuthT, StateT]) -> None:
|
|
26
|
+
def __init__(self, request: "Request[UserT, AuthT, StateT]") -> None:
|
|
29
27
|
"""Initialize :class:`InertiaDetails`"""
|
|
30
28
|
self.request = request
|
|
31
29
|
|
|
32
|
-
def _get_header_value(self, name: InertiaHeaders) -> str
|
|
30
|
+
def _get_header_value(self, name: "InertiaHeaders") -> "Optional[str]":
|
|
33
31
|
"""Parse request header
|
|
34
32
|
|
|
35
33
|
Check for uri encoded header and unquotes it in readable format.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
name: The header name.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
The header value.
|
|
36
40
|
"""
|
|
37
41
|
|
|
38
42
|
if value := self.request.headers.get(name.value.lower()):
|
|
@@ -40,10 +44,16 @@ class InertiaDetails:
|
|
|
40
44
|
return unquote(value) if is_uri_encoded else value
|
|
41
45
|
return None
|
|
42
46
|
|
|
43
|
-
def _get_route_component(self) -> str
|
|
47
|
+
def _get_route_component(self) -> "Optional[str]":
|
|
44
48
|
"""Get the route component.
|
|
45
49
|
|
|
46
|
-
Checks for the `component` key within the route
|
|
50
|
+
Checks for the `component` key within the route handler configuration.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
request: The request object.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
The route component.
|
|
47
57
|
"""
|
|
48
58
|
rh = self.request.scope.get("route_handler") # pyright: ignore[reportUnknownMemberType]
|
|
49
59
|
if rh:
|
|
@@ -51,37 +61,65 @@ class InertiaDetails:
|
|
|
51
61
|
return None
|
|
52
62
|
|
|
53
63
|
def __bool__(self) -> bool:
|
|
54
|
-
"""Check if request is sent by an Inertia client.
|
|
64
|
+
"""Check if request is sent by an Inertia client.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
True if the request is sent by an Inertia client, False otherwise.
|
|
68
|
+
"""
|
|
55
69
|
return self._get_header_value(InertiaHeaders.ENABLED) == "true"
|
|
56
70
|
|
|
57
71
|
@cached_property
|
|
58
|
-
def route_component(self) -> str
|
|
59
|
-
"""
|
|
72
|
+
def route_component(self) -> "Optional[str]":
|
|
73
|
+
"""Get the route component.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
The route component.
|
|
77
|
+
"""
|
|
60
78
|
return self._get_route_component()
|
|
61
79
|
|
|
62
80
|
@cached_property
|
|
63
|
-
def partial_component(self) -> str
|
|
64
|
-
"""
|
|
81
|
+
def partial_component(self) -> "Optional[str]":
|
|
82
|
+
"""Get the partial component.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The partial component.
|
|
86
|
+
"""
|
|
65
87
|
return self._get_header_value(InertiaHeaders.PARTIAL_COMPONENT)
|
|
66
88
|
|
|
67
89
|
@cached_property
|
|
68
|
-
def partial_data(self) -> str
|
|
69
|
-
"""
|
|
90
|
+
def partial_data(self) -> "Optional[str]":
|
|
91
|
+
"""Get the partial data.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
The partial data.
|
|
95
|
+
"""
|
|
70
96
|
return self._get_header_value(InertiaHeaders.PARTIAL_DATA)
|
|
71
97
|
|
|
72
98
|
@cached_property
|
|
73
|
-
def referer(self) -> str
|
|
74
|
-
"""
|
|
99
|
+
def referer(self) -> "Optional[str]":
|
|
100
|
+
"""Get the referer.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
The referer.
|
|
104
|
+
"""
|
|
75
105
|
return self._get_header_value(InertiaHeaders.REFERER)
|
|
76
106
|
|
|
77
107
|
@cached_property
|
|
78
108
|
def is_partial_render(self) -> bool:
|
|
79
|
-
"""
|
|
109
|
+
"""Check if the request is a partial render.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
True if the request is a partial render, False otherwise.
|
|
113
|
+
"""
|
|
80
114
|
return bool(self.partial_component == self.route_component and self.partial_data)
|
|
81
115
|
|
|
82
116
|
@cached_property
|
|
83
117
|
def partial_keys(self) -> list[str]:
|
|
84
|
-
"""
|
|
118
|
+
"""Get the partial keys.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
The partial keys.
|
|
122
|
+
"""
|
|
85
123
|
return self.partial_data.split(",") if self.partial_data is not None else []
|
|
86
124
|
|
|
87
125
|
|
|
@@ -90,7 +128,7 @@ class InertiaRequest(Request[UserT, AuthT, StateT]):
|
|
|
90
128
|
|
|
91
129
|
__slots__ = ("inertia",)
|
|
92
130
|
|
|
93
|
-
def __init__(self, scope: Scope, receive: Receive = empty_receive, send: Send = empty_send) -> None:
|
|
131
|
+
def __init__(self, scope: "Scope", receive: "Receive" = empty_receive, send: "Send" = empty_send) -> None:
|
|
94
132
|
"""Initialize :class:`InertiaRequest`"""
|
|
95
133
|
super().__init__(scope=scope, receive=receive, send=send)
|
|
96
134
|
self.inertia = InertiaDetails(self)
|
|
@@ -111,6 +149,6 @@ class InertiaRequest(Request[UserT, AuthT, StateT]):
|
|
|
111
149
|
return self.inertia.is_partial_render
|
|
112
150
|
|
|
113
151
|
@property
|
|
114
|
-
def partial_keys(self) -> set[str]:
|
|
152
|
+
def partial_keys(self) -> "set[str]":
|
|
115
153
|
"""True if the route handler contains an inertia enabled configuration."""
|
|
116
154
|
return set(self.inertia.partial_keys)
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
import itertools
|
|
4
|
-
from collections.abc import Mapping
|
|
2
|
+
from collections.abc import Iterable, Mapping
|
|
5
3
|
from mimetypes import guess_type
|
|
6
4
|
from pathlib import PurePath
|
|
7
5
|
from typing import (
|
|
8
6
|
TYPE_CHECKING,
|
|
9
7
|
Any,
|
|
10
|
-
|
|
8
|
+
Optional,
|
|
11
9
|
TypeVar,
|
|
10
|
+
Union,
|
|
12
11
|
cast,
|
|
13
12
|
)
|
|
14
13
|
from urllib.parse import quote, urlparse, urlunparse
|
|
@@ -54,16 +53,16 @@ class InertiaResponse(Response[T]):
|
|
|
54
53
|
self,
|
|
55
54
|
content: T,
|
|
56
55
|
*,
|
|
57
|
-
template_name: str
|
|
58
|
-
template_str: str
|
|
59
|
-
background: BackgroundTask | BackgroundTasks
|
|
60
|
-
context: dict[str, Any]
|
|
61
|
-
cookies: ResponseCookies
|
|
62
|
-
encoding: str = "utf-8",
|
|
63
|
-
headers: ResponseHeaders
|
|
64
|
-
media_type: MediaType
|
|
65
|
-
status_code: int = HTTP_200_OK,
|
|
66
|
-
type_encoders: TypeEncodersMap
|
|
56
|
+
template_name: "Optional[str]" = None,
|
|
57
|
+
template_str: "Optional[str]" = None,
|
|
58
|
+
background: "Optional[BackgroundTask | BackgroundTasks]" = None,
|
|
59
|
+
context: "Optional[dict[str, Any]]" = None,
|
|
60
|
+
cookies: "Optional[ResponseCookies]" = None,
|
|
61
|
+
encoding: "str" = "utf-8",
|
|
62
|
+
headers: "Optional[ResponseHeaders]" = None,
|
|
63
|
+
media_type: "Optional[Union[MediaType, str]]" = None,
|
|
64
|
+
status_code: "int" = HTTP_200_OK,
|
|
65
|
+
type_encoders: "Optional[TypeEncodersMap]" = None,
|
|
67
66
|
) -> None:
|
|
68
67
|
"""Handle the rendering of a given template into a bytes string.
|
|
69
68
|
|
|
@@ -83,6 +82,9 @@ class InertiaResponse(Response[T]):
|
|
|
83
82
|
the media type based on the template name. If this fails, fall back to ``text/plain``.
|
|
84
83
|
status_code: A value for the response HTTP status code.
|
|
85
84
|
type_encoders: A mapping of types to callables that transform them into types supported for serialization.
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
ValueError: If both template_name and template_str are provided.
|
|
86
88
|
"""
|
|
87
89
|
if template_name and template_str:
|
|
88
90
|
msg = "Either template_name or template_str must be provided, not both."
|
|
@@ -107,10 +109,10 @@ class InertiaResponse(Response[T]):
|
|
|
107
109
|
|
|
108
110
|
def create_template_context(
|
|
109
111
|
self,
|
|
110
|
-
request: Request[UserT, AuthT, StateT],
|
|
111
|
-
page_props: PageProps[T],
|
|
112
|
-
type_encoders: TypeEncodersMap
|
|
113
|
-
) -> dict[str, Any]:
|
|
112
|
+
request: "Request[UserT, AuthT, StateT]",
|
|
113
|
+
page_props: "PageProps[T]",
|
|
114
|
+
type_encoders: "Optional[TypeEncodersMap]" = None,
|
|
115
|
+
) -> "dict[str, Any]":
|
|
114
116
|
"""Create a context object for the template.
|
|
115
117
|
|
|
116
118
|
Args:
|
|
@@ -131,20 +133,20 @@ class InertiaResponse(Response[T]):
|
|
|
131
133
|
"csrf_input": f'<input type="hidden" name="_csrf_token" value="{csrf_token}" />',
|
|
132
134
|
}
|
|
133
135
|
|
|
134
|
-
def to_asgi_response( # noqa: C901
|
|
136
|
+
def to_asgi_response( # noqa: C901
|
|
135
137
|
self,
|
|
136
|
-
app: Litestar
|
|
137
|
-
request: Request[UserT, AuthT, StateT],
|
|
138
|
+
app: "Optional[Litestar]",
|
|
139
|
+
request: "Request[UserT, AuthT, StateT]",
|
|
138
140
|
*,
|
|
139
|
-
background: BackgroundTask
|
|
140
|
-
cookies: Iterable[Cookie]
|
|
141
|
-
encoded_headers: Iterable[tuple[bytes, bytes]]
|
|
142
|
-
headers: dict[str, str]
|
|
143
|
-
is_head_response: bool = False,
|
|
144
|
-
media_type: MediaType
|
|
145
|
-
status_code: int
|
|
146
|
-
type_encoders: TypeEncodersMap
|
|
147
|
-
) -> ASGIResponse:
|
|
141
|
+
background: "Optional[Union[BackgroundTask, BackgroundTasks]]" = None,
|
|
142
|
+
cookies: "Optional[Iterable[Cookie]]" = None,
|
|
143
|
+
encoded_headers: "Optional[Iterable[tuple[bytes, bytes]]]" = None,
|
|
144
|
+
headers: "Optional[dict[str, str]]" = None,
|
|
145
|
+
is_head_response: "bool" = False,
|
|
146
|
+
media_type: "Optional[Union[MediaType, str]]" = None,
|
|
147
|
+
status_code: "Optional[int]" = None,
|
|
148
|
+
type_encoders: "Optional[TypeEncodersMap]" = None,
|
|
149
|
+
) -> "ASGIResponse":
|
|
148
150
|
if app is not None:
|
|
149
151
|
warn_deprecation(
|
|
150
152
|
version="2.1",
|
|
@@ -262,9 +264,9 @@ class InertiaExternalRedirect(Response[Any]):
|
|
|
262
264
|
|
|
263
265
|
def __init__(
|
|
264
266
|
self,
|
|
265
|
-
request: Request[Any, Any, Any],
|
|
266
|
-
redirect_to: str,
|
|
267
|
-
**kwargs: Any,
|
|
267
|
+
request: "Request[Any, Any, Any]",
|
|
268
|
+
redirect_to: "str",
|
|
269
|
+
**kwargs: "Any",
|
|
268
270
|
) -> None:
|
|
269
271
|
"""Initialize external redirect, Set status code to 409 (required by Inertia),
|
|
270
272
|
and pass redirect url.
|
|
@@ -283,16 +285,16 @@ class InertiaRedirect(Redirect):
|
|
|
283
285
|
|
|
284
286
|
def __init__(
|
|
285
287
|
self,
|
|
286
|
-
request: Request[Any, Any, Any],
|
|
287
|
-
redirect_to: str,
|
|
288
|
-
**kwargs: Any,
|
|
288
|
+
request: "Request[Any, Any, Any]",
|
|
289
|
+
redirect_to: "str",
|
|
290
|
+
**kwargs: "Any",
|
|
289
291
|
) -> None:
|
|
290
292
|
"""Initialize external redirect, Set status code to 409 (required by Inertia),
|
|
291
293
|
and pass redirect url.
|
|
292
294
|
"""
|
|
293
295
|
referer = urlparse(request.headers.get("Referer", str(request.base_url)))
|
|
294
296
|
redirect_to = urlunparse(urlparse(redirect_to)._replace(scheme=referer.scheme))
|
|
295
|
-
super().__init__(
|
|
297
|
+
super().__init__( # pyright: ignore[reportUnknownMemberType]
|
|
296
298
|
path=redirect_to,
|
|
297
299
|
status_code=HTTP_307_TEMPORARY_REDIRECT if request.method == "GET" else HTTP_303_SEE_OTHER,
|
|
298
300
|
cookies=request.cookies,
|
|
@@ -305,13 +307,13 @@ class InertiaBack(Redirect):
|
|
|
305
307
|
|
|
306
308
|
def __init__(
|
|
307
309
|
self,
|
|
308
|
-
request: Request[Any, Any, Any],
|
|
309
|
-
**kwargs: Any,
|
|
310
|
+
request: "Request[Any, Any, Any]",
|
|
311
|
+
**kwargs: "Any",
|
|
310
312
|
) -> None:
|
|
311
313
|
"""Initialize external redirect, Set status code to 409 (required by Inertia),
|
|
312
314
|
and pass redirect url.
|
|
313
315
|
"""
|
|
314
|
-
super().__init__(
|
|
316
|
+
super().__init__( # pyright: ignore[reportUnknownMemberType]
|
|
315
317
|
path=request.headers.get("Referer", str(request.base_url)),
|
|
316
318
|
status_code=HTTP_307_TEMPORARY_REDIRECT if request.method == "GET" else HTTP_303_SEE_OTHER,
|
|
317
319
|
cookies=request.cookies,
|