litestar-vite 0.12.0__py3-none-any.whl → 0.13.0__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/commands.py +4 -4
- litestar_vite/inertia/__init__.py +12 -9
- litestar_vite/inertia/exception_handler.py +2 -1
- litestar_vite/inertia/helpers.py +329 -0
- litestar_vite/inertia/plugin.py +19 -7
- litestar_vite/inertia/response.py +18 -305
- litestar_vite/plugin.py +54 -31
- {litestar_vite-0.12.0.dist-info → litestar_vite-0.13.0.dist-info}/METADATA +1 -1
- {litestar_vite-0.12.0.dist-info → litestar_vite-0.13.0.dist-info}/RECORD +11 -10
- {litestar_vite-0.12.0.dist-info → litestar_vite-0.13.0.dist-info}/WHEEL +0 -0
- {litestar_vite-0.12.0.dist-info → litestar_vite-0.13.0.dist-info}/licenses/LICENSE +0 -0
litestar_vite/commands.py
CHANGED
|
@@ -13,11 +13,11 @@ VITE_INIT_TEMPLATES: set[str] = {"package.json.j2", "tsconfig.json.j2", "vite.co
|
|
|
13
13
|
DEFAULT_RESOURCES: set[str] = {"styles.css.j2", "main.ts.j2"}
|
|
14
14
|
DEFAULT_DEV_DEPENDENCIES: dict[str, str] = {
|
|
15
15
|
"typescript": "^5.7.2",
|
|
16
|
-
"vite": "^6.0.
|
|
17
|
-
"litestar-vite-plugin": "^0.
|
|
18
|
-
"@types/node": "^22.10.
|
|
16
|
+
"vite": "^6.0.6",
|
|
17
|
+
"litestar-vite-plugin": "^0.13.0",
|
|
18
|
+
"@types/node": "^22.10.2",
|
|
19
19
|
}
|
|
20
|
-
DEFAULT_DEPENDENCIES: dict[str, str] = {"axios": "^1.7.
|
|
20
|
+
DEFAULT_DEPENDENCIES: dict[str, str] = {"axios": "^1.7.9"}
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def to_json(value: Any) -> str:
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
from .
|
|
2
|
-
from .
|
|
3
|
-
from .
|
|
4
|
-
from .
|
|
5
|
-
from .
|
|
6
|
-
from .
|
|
1
|
+
from litestar_vite.inertia import helpers
|
|
2
|
+
from litestar_vite.inertia.config import InertiaConfig
|
|
3
|
+
from litestar_vite.inertia.exception_handler import create_inertia_exception_response, exception_to_http_response
|
|
4
|
+
from litestar_vite.inertia.helpers import error, get_shared_props, js_routes_script, lazy, share
|
|
5
|
+
from litestar_vite.inertia.middleware import InertiaMiddleware
|
|
6
|
+
from litestar_vite.inertia.plugin import InertiaPlugin
|
|
7
|
+
from litestar_vite.inertia.request import InertiaDetails, InertiaHeaders, InertiaRequest
|
|
8
|
+
from litestar_vite.inertia.response import (
|
|
7
9
|
InertiaBack,
|
|
8
10
|
InertiaExternalRedirect,
|
|
9
11
|
InertiaRedirect,
|
|
10
12
|
InertiaResponse,
|
|
11
|
-
error,
|
|
12
|
-
get_shared_props,
|
|
13
|
-
share,
|
|
14
13
|
)
|
|
14
|
+
|
|
15
15
|
from .routes import generate_js_routes
|
|
16
16
|
|
|
17
17
|
__all__ = (
|
|
@@ -30,5 +30,8 @@ __all__ = (
|
|
|
30
30
|
"exception_to_http_response",
|
|
31
31
|
"generate_js_routes",
|
|
32
32
|
"get_shared_props",
|
|
33
|
+
"helpers",
|
|
34
|
+
"js_routes_script",
|
|
35
|
+
"lazy",
|
|
33
36
|
"share",
|
|
34
37
|
)
|
|
@@ -35,7 +35,8 @@ from litestar.status_codes import (
|
|
|
35
35
|
HTTP_500_INTERNAL_SERVER_ERROR,
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
-
from litestar_vite.inertia.
|
|
38
|
+
from litestar_vite.inertia.helpers import error
|
|
39
|
+
from litestar_vite.inertia.response import InertiaBack, InertiaRedirect, InertiaResponse
|
|
39
40
|
|
|
40
41
|
if TYPE_CHECKING:
|
|
41
42
|
from litestar.connection import Request
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from functools import lru_cache
|
|
8
|
+
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
|
+
)
|
|
23
|
+
|
|
24
|
+
from anyio.from_thread import BlockingPortal, start_blocking_portal
|
|
25
|
+
from litestar.exceptions import ImproperlyConfiguredException
|
|
26
|
+
from litestar.utils.empty import value_or_default
|
|
27
|
+
from litestar.utils.scope.state import ScopeState
|
|
28
|
+
from markupsafe import Markup
|
|
29
|
+
from typing_extensions import ParamSpec, TypeGuard
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from litestar.connection import ASGIConnection
|
|
33
|
+
|
|
34
|
+
from litestar_vite.inertia.plugin import InertiaPlugin
|
|
35
|
+
from litestar_vite.inertia.routes import Routes
|
|
36
|
+
|
|
37
|
+
T = TypeVar("T")
|
|
38
|
+
T_ParamSpec = ParamSpec("T_ParamSpec")
|
|
39
|
+
PropKeyT = TypeVar("PropKeyT", bound=str)
|
|
40
|
+
StaticT = TypeVar("StaticT", bound=object)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@overload
|
|
44
|
+
def lazy(key: str, value_or_callable: None) -> StaticProp[str, None]: ...
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@overload
|
|
48
|
+
def lazy(key: str, value_or_callable: T) -> StaticProp[str, T]: ...
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@overload
|
|
52
|
+
def lazy(key: str, value_or_callable: Callable[..., None] = ...) -> DeferredProp[str, None]: ...
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@overload
|
|
56
|
+
def lazy(key: str, value_or_callable: Callable[..., Coroutine[Any, Any, None]] = ...) -> DeferredProp[str, None]: ...
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@overload
|
|
60
|
+
def lazy(
|
|
61
|
+
key: str,
|
|
62
|
+
value_or_callable: Callable[..., T | Coroutine[Any, Any, T]] = ..., # pyright: ignore[reportInvalidTypeVarUse]
|
|
63
|
+
) -> DeferredProp[str, T]: ...
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def lazy(
|
|
67
|
+
key: str,
|
|
68
|
+
value_or_callable: None
|
|
69
|
+
| Callable[T_ParamSpec, None | Coroutine[Any, Any, None]]
|
|
70
|
+
| T
|
|
71
|
+
| Callable[T_ParamSpec, T | Coroutine[Any, Any, T]] = None,
|
|
72
|
+
) -> StaticProp[str, None] | StaticProp[str, T] | DeferredProp[str, T] | DeferredProp[str, None]:
|
|
73
|
+
"""Wrap an async function to return a DeferredProp."""
|
|
74
|
+
if value_or_callable is None:
|
|
75
|
+
return StaticProp[str, None](key=key, value=None)
|
|
76
|
+
|
|
77
|
+
if not callable(value_or_callable):
|
|
78
|
+
return StaticProp[str, T](key=key, value=value_or_callable)
|
|
79
|
+
|
|
80
|
+
return DeferredProp[str, T](key=key, value=cast("Callable[..., T | Coroutine[Any, Any, T]]", value_or_callable))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class StaticProp(Generic[PropKeyT, StaticT]):
|
|
84
|
+
"""A wrapper for static property evaluation."""
|
|
85
|
+
|
|
86
|
+
def __init__(self, key: PropKeyT, value: StaticT) -> None:
|
|
87
|
+
self._key = key
|
|
88
|
+
self._result = value
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def key(self) -> PropKeyT:
|
|
92
|
+
return self._key
|
|
93
|
+
|
|
94
|
+
def render(self, portal: BlockingPortal | None = None) -> StaticT:
|
|
95
|
+
return self._result
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class DeferredProp(Generic[PropKeyT, T]):
|
|
99
|
+
"""A wrapper for deferred property evaluation."""
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self, key: PropKeyT, value: Callable[..., None | T | Coroutine[Any, Any, T | None]] | None = None
|
|
103
|
+
) -> None:
|
|
104
|
+
self._key = key
|
|
105
|
+
self._value = value
|
|
106
|
+
self._evaluated = False
|
|
107
|
+
self._result: T | None = None
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def key(self) -> PropKeyT:
|
|
111
|
+
return self._key
|
|
112
|
+
|
|
113
|
+
@contextmanager
|
|
114
|
+
def with_portal(self, portal: BlockingPortal | None = None) -> Generator[BlockingPortal, None, None]:
|
|
115
|
+
if portal is None:
|
|
116
|
+
with start_blocking_portal() as p:
|
|
117
|
+
yield p
|
|
118
|
+
else:
|
|
119
|
+
yield portal
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def _is_awaitable(
|
|
123
|
+
v: Callable[..., T | Coroutine[Any, Any, T]],
|
|
124
|
+
) -> TypeGuard[Coroutine[Any, Any, T]]:
|
|
125
|
+
return inspect.iscoroutinefunction(v)
|
|
126
|
+
|
|
127
|
+
def render(self, portal: BlockingPortal | None = None) -> T | None:
|
|
128
|
+
if self._evaluated:
|
|
129
|
+
return self._result
|
|
130
|
+
if self._value is None or not callable(self._value):
|
|
131
|
+
self._result = self._value
|
|
132
|
+
self._evaluated = True
|
|
133
|
+
return self._result
|
|
134
|
+
if not self._is_awaitable(cast("Callable[..., T]", self._value)):
|
|
135
|
+
self._result = cast("T", self._value())
|
|
136
|
+
self._evaluated = True
|
|
137
|
+
return self._result
|
|
138
|
+
with self.with_portal(portal) as p:
|
|
139
|
+
self._result = p.call(cast("Callable[..., T]", self._value))
|
|
140
|
+
self._evaluated = True
|
|
141
|
+
return self._result
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def is_lazy_prop(value: Any) -> TypeGuard[DeferredProp[Any, Any]]:
|
|
145
|
+
"""Check if value is a deferred property.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
value: Any value to check
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
bool: True if value is a deferred property
|
|
152
|
+
"""
|
|
153
|
+
return isinstance(value, (DeferredProp, StaticProp))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def should_render(value: Any, partial_data: set[str] | None = None) -> bool:
|
|
157
|
+
"""Check if value should be rendered.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
value: Any value to check
|
|
161
|
+
partial_data: Optional set of keys for partial rendering
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
bool: True if value should be rendered
|
|
165
|
+
"""
|
|
166
|
+
partial_data = partial_data or set()
|
|
167
|
+
if is_lazy_prop(value):
|
|
168
|
+
return value.key in partial_data
|
|
169
|
+
return True
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def is_or_contains_lazy_prop(value: Any) -> bool:
|
|
173
|
+
"""Check if value is or contains a deferred property.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
value: Any value to check
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
bool: True if value is or contains a deferred property
|
|
180
|
+
"""
|
|
181
|
+
if is_lazy_prop(value):
|
|
182
|
+
return True
|
|
183
|
+
if isinstance(value, str):
|
|
184
|
+
return False
|
|
185
|
+
if isinstance(value, Mapping):
|
|
186
|
+
return any(is_or_contains_lazy_prop(v) for v in cast("Mapping[str, Any]", value).values())
|
|
187
|
+
if isinstance(value, Iterable):
|
|
188
|
+
return any(is_or_contains_lazy_prop(v) for v in cast("Iterable[Any]", value))
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def lazy_render(value: T, partial_data: set[str] | None = None, portal: BlockingPortal | None = None) -> T:
|
|
193
|
+
"""Filter deferred properties from the value based on partial data.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
value: The value to filter
|
|
197
|
+
partial_data: Keys for partial rendering
|
|
198
|
+
portal: Optional portal to use for async rendering
|
|
199
|
+
Returns:
|
|
200
|
+
The filtered value
|
|
201
|
+
"""
|
|
202
|
+
partial_data = partial_data or set()
|
|
203
|
+
if isinstance(value, str):
|
|
204
|
+
return cast("T", value)
|
|
205
|
+
if isinstance(value, Mapping):
|
|
206
|
+
return cast(
|
|
207
|
+
"T",
|
|
208
|
+
{
|
|
209
|
+
k: lazy_render(v, partial_data, portal)
|
|
210
|
+
for k, v in cast("Mapping[str, Any]", value).items()
|
|
211
|
+
if should_render(v, partial_data)
|
|
212
|
+
},
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
if isinstance(value, (list, tuple)):
|
|
216
|
+
filtered = [
|
|
217
|
+
lazy_render(v, partial_data, portal) for v in cast("Iterable[Any]", value) if should_render(v, partial_data)
|
|
218
|
+
]
|
|
219
|
+
return cast("T", type(value)(filtered)) # pyright: ignore[reportUnknownArgumentType]
|
|
220
|
+
|
|
221
|
+
if is_lazy_prop(value) and should_render(value, partial_data):
|
|
222
|
+
return cast("T", value.render(portal))
|
|
223
|
+
|
|
224
|
+
return cast("T", value)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def get_shared_props(
|
|
228
|
+
request: ASGIConnection[Any, Any, Any, Any],
|
|
229
|
+
partial_data: set[str] | None = None,
|
|
230
|
+
) -> dict[str, Any]:
|
|
231
|
+
"""Return shared session props for a request.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
request: The ASGI connection.
|
|
235
|
+
partial_data: Optional set of keys for partial rendering.
|
|
236
|
+
portal: Optional portal to use for async rendering
|
|
237
|
+
Returns:
|
|
238
|
+
Dict[str, Any]: The shared props.
|
|
239
|
+
|
|
240
|
+
Note:
|
|
241
|
+
Be sure to call this before `self.create_template_context` if you would like to include the `flash` message details.
|
|
242
|
+
"""
|
|
243
|
+
props: dict[str, Any] = {}
|
|
244
|
+
flash: dict[str, list[str]] = defaultdict(list)
|
|
245
|
+
errors: dict[str, Any] = {}
|
|
246
|
+
error_bag = request.headers.get("X-Inertia-Error-Bag", None)
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
errors = request.session.pop("_errors", {})
|
|
250
|
+
shared_props = cast("Dict[str,Any]", request.session.pop("_shared", {}))
|
|
251
|
+
inertia_plugin = cast("InertiaPlugin", request.app.plugins.get("InertiaPlugin"))
|
|
252
|
+
|
|
253
|
+
# Handle deferred props
|
|
254
|
+
for key, value in shared_props.items():
|
|
255
|
+
if is_lazy_prop(value) and should_render(value, partial_data):
|
|
256
|
+
props[key] = value.render(inertia_plugin.portal)
|
|
257
|
+
continue
|
|
258
|
+
if should_render(value, partial_data):
|
|
259
|
+
props[key] = value
|
|
260
|
+
|
|
261
|
+
for message in cast("List[Dict[str,Any]]", request.session.pop("_messages", [])):
|
|
262
|
+
flash[message["category"]].append(message["message"])
|
|
263
|
+
|
|
264
|
+
props.update(inertia_plugin.config.extra_static_page_props)
|
|
265
|
+
for session_prop in inertia_plugin.config.extra_session_page_props:
|
|
266
|
+
if session_prop not in props and session_prop in request.session:
|
|
267
|
+
props[session_prop] = request.session.get(session_prop)
|
|
268
|
+
|
|
269
|
+
except (AttributeError, ImproperlyConfiguredException):
|
|
270
|
+
msg = "Unable to generate all shared props. A valid session was not found for this request."
|
|
271
|
+
request.logger.warning(msg)
|
|
272
|
+
|
|
273
|
+
props["flash"] = flash
|
|
274
|
+
props["errors"] = {error_bag: errors} if error_bag is not None else errors
|
|
275
|
+
props["csrf_token"] = value_or_default(ScopeState.from_scope(request.scope).csrf_token, "")
|
|
276
|
+
return props
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def share(
|
|
280
|
+
connection: ASGIConnection[Any, Any, Any, Any],
|
|
281
|
+
key: str,
|
|
282
|
+
value: Any,
|
|
283
|
+
) -> None:
|
|
284
|
+
"""Share a value in the session.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
connection: The ASGI connection.
|
|
288
|
+
key: The key to store the value under.
|
|
289
|
+
value: The value to store.
|
|
290
|
+
"""
|
|
291
|
+
try:
|
|
292
|
+
connection.session.setdefault("_shared", {}).update({key: value})
|
|
293
|
+
except (AttributeError, ImproperlyConfiguredException):
|
|
294
|
+
msg = "Unable to set `share` session state. A valid session was not found for this request."
|
|
295
|
+
connection.logger.warning(msg)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def error(
|
|
299
|
+
connection: ASGIConnection[Any, Any, Any, Any],
|
|
300
|
+
key: str,
|
|
301
|
+
message: str,
|
|
302
|
+
) -> None:
|
|
303
|
+
"""Set an error message in the session.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
connection: The ASGI connection.
|
|
307
|
+
key: The key to store the error under.
|
|
308
|
+
message: The error message.
|
|
309
|
+
"""
|
|
310
|
+
try:
|
|
311
|
+
connection.session.setdefault("_errors", {}).update({key: message})
|
|
312
|
+
except (AttributeError, ImproperlyConfiguredException):
|
|
313
|
+
msg = "Unable to set `error` session state. A valid session was not found for this request."
|
|
314
|
+
connection.logger.warning(msg)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def js_routes_script(js_routes: Routes) -> Markup:
|
|
318
|
+
@lru_cache
|
|
319
|
+
def _markup_safe_json_dumps(js_routes: str) -> Markup:
|
|
320
|
+
js = js_routes.replace("<", "\\u003c").replace(">", "\\u003e").replace("&", "\\u0026").replace("'", "\\u0027")
|
|
321
|
+
return Markup(js)
|
|
322
|
+
|
|
323
|
+
return Markup(
|
|
324
|
+
dedent(f"""
|
|
325
|
+
<script type="module">
|
|
326
|
+
globalThis.routes = JSON.parse('{_markup_safe_json_dumps(js_routes.formatted_routes)}')
|
|
327
|
+
</script>
|
|
328
|
+
"""),
|
|
329
|
+
)
|
litestar_vite/inertia/plugin.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from contextlib import asynccontextmanager
|
|
4
|
+
from typing import TYPE_CHECKING, AsyncGenerator
|
|
4
5
|
|
|
5
|
-
from anyio.from_thread import
|
|
6
|
+
from anyio.from_thread import start_blocking_portal
|
|
6
7
|
from litestar.plugins import InitPluginProtocol
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
10
|
+
from anyio.from_thread import BlockingPortal
|
|
9
11
|
from litestar import Litestar
|
|
10
12
|
from litestar.config.app import AppConfig
|
|
11
13
|
|
|
@@ -23,7 +25,7 @@ def set_js_routes(app: Litestar) -> None:
|
|
|
23
25
|
class InertiaPlugin(InitPluginProtocol):
|
|
24
26
|
"""Inertia plugin."""
|
|
25
27
|
|
|
26
|
-
__slots__ = ("
|
|
28
|
+
__slots__ = ("_portal", "config")
|
|
27
29
|
|
|
28
30
|
def __init__(self, config: InertiaConfig) -> None:
|
|
29
31
|
"""Initialize ``Inertia``.
|
|
@@ -33,10 +35,18 @@ class InertiaPlugin(InitPluginProtocol):
|
|
|
33
35
|
"""
|
|
34
36
|
self.config = config
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
@asynccontextmanager
|
|
39
|
+
async def lifespan(self, app: Litestar) -> AsyncGenerator[None, None]:
|
|
40
|
+
"""Lifespan to ensure the event loop is available."""
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
with start_blocking_portal() as portal:
|
|
43
|
+
self._portal = portal
|
|
44
|
+
yield
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def portal(self) -> BlockingPortal:
|
|
48
|
+
"""Get the portal."""
|
|
49
|
+
return self._portal
|
|
40
50
|
|
|
41
51
|
def on_app_init(self, app_config: AppConfig) -> AppConfig:
|
|
42
52
|
"""Configure application for use with Vite.
|
|
@@ -52,9 +62,10 @@ class InertiaPlugin(InitPluginProtocol):
|
|
|
52
62
|
from litestar.utils.predicates import is_class_and_subclass
|
|
53
63
|
|
|
54
64
|
from litestar_vite.inertia.exception_handler import exception_to_http_response
|
|
65
|
+
from litestar_vite.inertia.helpers import DeferredProp, StaticProp
|
|
55
66
|
from litestar_vite.inertia.middleware import InertiaMiddleware
|
|
56
67
|
from litestar_vite.inertia.request import InertiaRequest
|
|
57
|
-
from litestar_vite.inertia.response import
|
|
68
|
+
from litestar_vite.inertia.response import InertiaBack, InertiaResponse
|
|
58
69
|
|
|
59
70
|
for mw in app_config.middleware:
|
|
60
71
|
if isinstance(mw, DefineMiddleware) and is_class_and_subclass(
|
|
@@ -81,4 +92,5 @@ class InertiaPlugin(InitPluginProtocol):
|
|
|
81
92
|
(lambda x: x is DeferredProp, lambda t, v: t(v)),
|
|
82
93
|
*(app_config.type_decoders or []),
|
|
83
94
|
]
|
|
95
|
+
app_config.lifespan.append(self.lifespan) # pyright: ignore[reportUnknownMemberType]
|
|
84
96
|
return app_config
|
|
@@ -1,31 +1,18 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import inspect
|
|
4
3
|
import itertools
|
|
5
|
-
from collections import defaultdict
|
|
6
4
|
from collections.abc import Mapping
|
|
7
|
-
from contextlib import contextmanager
|
|
8
|
-
from functools import lru_cache
|
|
9
5
|
from mimetypes import guess_type
|
|
10
6
|
from pathlib import PurePath
|
|
11
|
-
from textwrap import dedent
|
|
12
7
|
from typing import (
|
|
13
8
|
TYPE_CHECKING,
|
|
14
9
|
Any,
|
|
15
|
-
Callable,
|
|
16
|
-
Coroutine,
|
|
17
|
-
Dict,
|
|
18
|
-
Generator,
|
|
19
|
-
Generic,
|
|
20
10
|
Iterable,
|
|
21
|
-
List,
|
|
22
11
|
TypeVar,
|
|
23
12
|
cast,
|
|
24
|
-
overload,
|
|
25
13
|
)
|
|
26
14
|
from urllib.parse import quote, urlparse, urlunparse
|
|
27
15
|
|
|
28
|
-
from anyio.from_thread import BlockingPortal, start_blocking_portal
|
|
29
16
|
from litestar import Litestar, MediaType, Request, Response
|
|
30
17
|
from litestar.datastructures.cookie import Cookie
|
|
31
18
|
from litestar.exceptions import ImproperlyConfiguredException
|
|
@@ -37,308 +24,27 @@ from litestar.utils.deprecation import warn_deprecation
|
|
|
37
24
|
from litestar.utils.empty import value_or_default
|
|
38
25
|
from litestar.utils.helpers import get_enum_string_value
|
|
39
26
|
from litestar.utils.scope.state import ScopeState
|
|
40
|
-
from markupsafe import Markup
|
|
41
|
-
from typing_extensions import ParamSpec, TypeGuard
|
|
42
27
|
|
|
43
28
|
from litestar_vite.inertia._utils import get_headers
|
|
29
|
+
from litestar_vite.inertia.helpers import (
|
|
30
|
+
get_shared_props,
|
|
31
|
+
is_or_contains_lazy_prop,
|
|
32
|
+
js_routes_script,
|
|
33
|
+
lazy_render,
|
|
34
|
+
should_render,
|
|
35
|
+
)
|
|
36
|
+
from litestar_vite.inertia.plugin import InertiaPlugin
|
|
44
37
|
from litestar_vite.inertia.types import InertiaHeaderType, PageProps
|
|
45
38
|
from litestar_vite.plugin import VitePlugin
|
|
46
39
|
|
|
47
40
|
if TYPE_CHECKING:
|
|
48
41
|
from litestar.app import Litestar
|
|
49
42
|
from litestar.background_tasks import BackgroundTask, BackgroundTasks
|
|
50
|
-
from litestar.connection import ASGIConnection
|
|
51
43
|
from litestar.connection.base import AuthT, StateT, UserT
|
|
52
44
|
from litestar.types import ResponseCookies, ResponseHeaders, TypeEncodersMap
|
|
53
45
|
|
|
54
|
-
from litestar_vite.inertia.plugin import InertiaPlugin
|
|
55
|
-
from litestar_vite.inertia.routes import Routes
|
|
56
46
|
|
|
57
47
|
T = TypeVar("T")
|
|
58
|
-
T_ParamSpec = ParamSpec("T_ParamSpec")
|
|
59
|
-
PropKeyT = TypeVar("PropKeyT", bound=str)
|
|
60
|
-
StaticT = TypeVar("StaticT", bound=object)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
@overload
|
|
64
|
-
def lazy(key: str, value_or_callable: None) -> StaticProp[str, None]: ...
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@overload
|
|
68
|
-
def lazy(key: str, value_or_callable: T) -> StaticProp[str, T]: ...
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@overload
|
|
72
|
-
def lazy(
|
|
73
|
-
key: str,
|
|
74
|
-
value_or_callable: Callable[..., None] = ...,
|
|
75
|
-
) -> DeferredProp[str, None]: ...
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@overload
|
|
79
|
-
def lazy(
|
|
80
|
-
key: str,
|
|
81
|
-
value_or_callable: Callable[T_ParamSpec, T | Coroutine[Any, Any, T]] = ..., # pyright: ignore[reportInvalidTypeVarUse]
|
|
82
|
-
) -> DeferredProp[str, T]: ...
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def lazy( # type: ignore[misc]
|
|
86
|
-
key: str,
|
|
87
|
-
value_or_callable: T | Callable[T_ParamSpec, T | Coroutine[Any, Any, T]], # pyright: ignore[reportInvalidTypeVarUse]
|
|
88
|
-
) -> StaticProp[str, None] | StaticProp[str, T] | DeferredProp[str, T] | DeferredProp[str, None]:
|
|
89
|
-
"""Wrap an async function to return a DeferredProp."""
|
|
90
|
-
if value_or_callable is None:
|
|
91
|
-
return StaticProp[str, None](key=key, value=None)
|
|
92
|
-
|
|
93
|
-
if not callable(value_or_callable):
|
|
94
|
-
return StaticProp[str, T](key=key, value=value_or_callable)
|
|
95
|
-
|
|
96
|
-
return DeferredProp[str, T](key=key, value=value_or_callable) # pyright: ignore[reportArgumentType]
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
class StaticProp(Generic[PropKeyT, StaticT]):
|
|
100
|
-
"""A wrapper for static property evaluation."""
|
|
101
|
-
|
|
102
|
-
def __init__(self, key: PropKeyT, value: StaticT) -> None:
|
|
103
|
-
self._key = key
|
|
104
|
-
self._result = value
|
|
105
|
-
|
|
106
|
-
@property
|
|
107
|
-
def key(self) -> PropKeyT:
|
|
108
|
-
return self._key
|
|
109
|
-
|
|
110
|
-
def render(self, portal: BlockingPortal | None = None) -> StaticT:
|
|
111
|
-
return self._result
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
class DeferredProp(Generic[PropKeyT, T]):
|
|
115
|
-
"""A wrapper for deferred property evaluation."""
|
|
116
|
-
|
|
117
|
-
def __init__(self, key: PropKeyT, value: Callable[T_ParamSpec, T | Coroutine[Any, Any, T]] | None = None) -> None:
|
|
118
|
-
self._key = key
|
|
119
|
-
self._value = value
|
|
120
|
-
self._evaluated = False
|
|
121
|
-
self._result: T | None = None
|
|
122
|
-
|
|
123
|
-
@property
|
|
124
|
-
def key(self) -> PropKeyT:
|
|
125
|
-
return self._key
|
|
126
|
-
|
|
127
|
-
@staticmethod
|
|
128
|
-
def _is_awaitable(
|
|
129
|
-
v: Callable[T_ParamSpec, T | Coroutine[Any, Any, T]],
|
|
130
|
-
) -> TypeGuard[Coroutine[Any, Any, T]]:
|
|
131
|
-
return inspect.iscoroutinefunction(v)
|
|
132
|
-
|
|
133
|
-
@staticmethod
|
|
134
|
-
@contextmanager
|
|
135
|
-
def _with_portal(portal: BlockingPortal | None = None) -> Generator[BlockingPortal, None, None]:
|
|
136
|
-
if portal is None:
|
|
137
|
-
with start_blocking_portal() as new_portal:
|
|
138
|
-
yield new_portal
|
|
139
|
-
else:
|
|
140
|
-
yield portal
|
|
141
|
-
|
|
142
|
-
def render(self, portal: BlockingPortal | None = None) -> T | None:
|
|
143
|
-
if self._evaluated:
|
|
144
|
-
return self._result
|
|
145
|
-
if self._value is None or not callable(self._value):
|
|
146
|
-
self._result = self._value
|
|
147
|
-
elif not self._is_awaitable(self._value):
|
|
148
|
-
self._result = self._value() # type: ignore[call-arg,assignment,unused-ignore]
|
|
149
|
-
else:
|
|
150
|
-
with self._with_portal(portal) as bp:
|
|
151
|
-
self._result = bp.call(self._value) # type: ignore[call-overload]
|
|
152
|
-
self._evaluated = True
|
|
153
|
-
return self._result # pyright: ignore[reportUnknownVariableType,reportUnknownMemberType]
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def is_lazy_prop(value: Any) -> TypeGuard[DeferredProp[Any, Any]]:
|
|
157
|
-
"""Check if value is a deferred property.
|
|
158
|
-
|
|
159
|
-
Args:
|
|
160
|
-
value: Any value to check
|
|
161
|
-
|
|
162
|
-
Returns:
|
|
163
|
-
bool: True if value is a deferred property
|
|
164
|
-
"""
|
|
165
|
-
return isinstance(value, (DeferredProp, StaticProp))
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def should_render(value: Any, partial_data: set[str] | None = None) -> bool:
|
|
169
|
-
"""Check if value should be rendered.
|
|
170
|
-
|
|
171
|
-
Args:
|
|
172
|
-
value: Any value to check
|
|
173
|
-
partial_data: Optional set of keys for partial rendering
|
|
174
|
-
|
|
175
|
-
Returns:
|
|
176
|
-
bool: True if value should be rendered
|
|
177
|
-
"""
|
|
178
|
-
partial_data = partial_data or set()
|
|
179
|
-
if is_lazy_prop(value):
|
|
180
|
-
return value.key in partial_data
|
|
181
|
-
return True
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def is_or_contains_lazy_prop(value: Any) -> bool:
|
|
185
|
-
"""Check if value is or contains a deferred property.
|
|
186
|
-
|
|
187
|
-
Args:
|
|
188
|
-
value: Any value to check
|
|
189
|
-
|
|
190
|
-
Returns:
|
|
191
|
-
bool: True if value is or contains a deferred property
|
|
192
|
-
"""
|
|
193
|
-
if is_lazy_prop(value):
|
|
194
|
-
return True
|
|
195
|
-
if isinstance(value, str):
|
|
196
|
-
return False
|
|
197
|
-
if isinstance(value, Mapping):
|
|
198
|
-
return any(is_or_contains_lazy_prop(v) for v in cast("Mapping[str, Any]", value).values())
|
|
199
|
-
if isinstance(value, Iterable):
|
|
200
|
-
return any(is_or_contains_lazy_prop(v) for v in cast("Iterable[Any]", value))
|
|
201
|
-
return False
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def lazy_render(value: T, partial_data: set[str] | None = None, portal: BlockingPortal | None = None) -> T:
|
|
205
|
-
"""Filter deferred properties from the value based on partial data.
|
|
206
|
-
|
|
207
|
-
Args:
|
|
208
|
-
value: The value to filter
|
|
209
|
-
partial_data: Keys for partial rendering
|
|
210
|
-
portal: Optional portal to use for async rendering
|
|
211
|
-
Returns:
|
|
212
|
-
The filtered value
|
|
213
|
-
"""
|
|
214
|
-
partial_data = partial_data or set()
|
|
215
|
-
if isinstance(value, str):
|
|
216
|
-
return cast("T", value)
|
|
217
|
-
if isinstance(value, Mapping):
|
|
218
|
-
return cast(
|
|
219
|
-
"T",
|
|
220
|
-
{
|
|
221
|
-
k: lazy_render(v, partial_data)
|
|
222
|
-
for k, v in cast("Mapping[str, Any]", value).items()
|
|
223
|
-
if should_render(v, partial_data)
|
|
224
|
-
},
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
if isinstance(value, (list, tuple)):
|
|
228
|
-
filtered = [
|
|
229
|
-
lazy_render(v, partial_data) for v in cast("Iterable[Any]", value) if should_render(v, partial_data)
|
|
230
|
-
]
|
|
231
|
-
return cast("T", type(value)(filtered)) # pyright: ignore[reportUnknownArgumentType]
|
|
232
|
-
|
|
233
|
-
if is_lazy_prop(value) and should_render(value, partial_data):
|
|
234
|
-
return cast("T", value.render())
|
|
235
|
-
|
|
236
|
-
return cast("T", value)
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
def get_shared_props(
|
|
240
|
-
request: ASGIConnection[Any, Any, Any, Any],
|
|
241
|
-
partial_data: set[str] | None = None,
|
|
242
|
-
) -> dict[str, Any]:
|
|
243
|
-
"""Return shared session props for a request.
|
|
244
|
-
|
|
245
|
-
Args:
|
|
246
|
-
request: The ASGI connection.
|
|
247
|
-
partial_data: Optional set of keys for partial rendering.
|
|
248
|
-
|
|
249
|
-
Returns:
|
|
250
|
-
Dict[str, Any]: The shared props.
|
|
251
|
-
|
|
252
|
-
Note:
|
|
253
|
-
Be sure to call this before `self.create_template_context` if you would like to include the `flash` message details.
|
|
254
|
-
"""
|
|
255
|
-
props: dict[str, Any] = {}
|
|
256
|
-
flash: dict[str, list[str]] = defaultdict(list)
|
|
257
|
-
errors: dict[str, Any] = {}
|
|
258
|
-
error_bag = request.headers.get("X-Inertia-Error-Bag", None)
|
|
259
|
-
|
|
260
|
-
try:
|
|
261
|
-
errors = request.session.pop("_errors", {})
|
|
262
|
-
shared_props = cast("Dict[str,Any]", request.session.pop("_shared", {}))
|
|
263
|
-
|
|
264
|
-
# Handle deferred props
|
|
265
|
-
for key, value in shared_props.items():
|
|
266
|
-
if is_lazy_prop(value) and should_render(value, partial_data):
|
|
267
|
-
props[key] = value.render()
|
|
268
|
-
continue
|
|
269
|
-
if should_render(value, partial_data):
|
|
270
|
-
props[key] = value
|
|
271
|
-
|
|
272
|
-
for message in cast("List[Dict[str,Any]]", request.session.pop("_messages", [])):
|
|
273
|
-
flash[message["category"]].append(message["message"])
|
|
274
|
-
|
|
275
|
-
inertia_plugin = cast("InertiaPlugin", request.app.plugins.get("InertiaPlugin"))
|
|
276
|
-
props.update(inertia_plugin.config.extra_static_page_props)
|
|
277
|
-
for session_prop in inertia_plugin.config.extra_session_page_props:
|
|
278
|
-
if session_prop not in props and session_prop in request.session:
|
|
279
|
-
props[session_prop] = request.session.get(session_prop)
|
|
280
|
-
|
|
281
|
-
except (AttributeError, ImproperlyConfiguredException):
|
|
282
|
-
msg = "Unable to generate all shared props. A valid session was not found for this request."
|
|
283
|
-
request.logger.warning(msg)
|
|
284
|
-
|
|
285
|
-
props["flash"] = flash
|
|
286
|
-
props["errors"] = {error_bag: errors} if error_bag is not None else errors
|
|
287
|
-
props["csrf_token"] = value_or_default(ScopeState.from_scope(request.scope).csrf_token, "")
|
|
288
|
-
return props
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
def share(
|
|
292
|
-
connection: ASGIConnection[Any, Any, Any, Any],
|
|
293
|
-
key: str,
|
|
294
|
-
value: Any,
|
|
295
|
-
) -> None:
|
|
296
|
-
"""Share a value in the session.
|
|
297
|
-
|
|
298
|
-
Args:
|
|
299
|
-
connection: The ASGI connection.
|
|
300
|
-
key: The key to store the value under.
|
|
301
|
-
value: The value to store.
|
|
302
|
-
"""
|
|
303
|
-
try:
|
|
304
|
-
connection.session.setdefault("_shared", {}).update({key: value})
|
|
305
|
-
except (AttributeError, ImproperlyConfiguredException):
|
|
306
|
-
msg = "Unable to set `share` session state. A valid session was not found for this request."
|
|
307
|
-
connection.logger.warning(msg)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
def error(
|
|
311
|
-
connection: ASGIConnection[Any, Any, Any, Any],
|
|
312
|
-
key: str,
|
|
313
|
-
message: str,
|
|
314
|
-
) -> None:
|
|
315
|
-
"""Set an error message in the session.
|
|
316
|
-
|
|
317
|
-
Args:
|
|
318
|
-
connection: The ASGI connection.
|
|
319
|
-
key: The key to store the error under.
|
|
320
|
-
message: The error message.
|
|
321
|
-
"""
|
|
322
|
-
try:
|
|
323
|
-
connection.session.setdefault("_errors", {}).update({key: message})
|
|
324
|
-
except (AttributeError, ImproperlyConfiguredException):
|
|
325
|
-
msg = "Unable to set `error` session state. A valid session was not found for this request."
|
|
326
|
-
connection.logger.warning(msg)
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
def js_routes_script(js_routes: Routes) -> Markup:
|
|
330
|
-
@lru_cache
|
|
331
|
-
def _markup_safe_json_dumps(js_routes: str) -> Markup:
|
|
332
|
-
js = js_routes.replace("<", "\\u003c").replace(">", "\\u003e").replace("&", "\\u0026").replace("'", "\\u0027")
|
|
333
|
-
return Markup(js)
|
|
334
|
-
|
|
335
|
-
return Markup(
|
|
336
|
-
dedent(f"""
|
|
337
|
-
<script type="module">
|
|
338
|
-
globalThis.routes = JSON.parse('{_markup_safe_json_dumps(js_routes.formatted_routes)}')
|
|
339
|
-
</script>
|
|
340
|
-
"""),
|
|
341
|
-
)
|
|
342
48
|
|
|
343
49
|
|
|
344
50
|
class InertiaResponse(Response[T]):
|
|
@@ -473,13 +179,21 @@ class InertiaResponse(Response[T]):
|
|
|
473
179
|
is_partial_render = cast("bool", getattr(request, "is_partial_render", False))
|
|
474
180
|
partial_keys = cast("set[str]", getattr(request, "partial_keys", {}))
|
|
475
181
|
vite_plugin = request.app.plugins.get(VitePlugin)
|
|
182
|
+
inertia_plugin = request.app.plugins.get(InertiaPlugin)
|
|
476
183
|
template_engine = request.app.template_engine # pyright: ignore[reportUnknownVariableType,reportUnknownMemberType]
|
|
477
184
|
headers.update(
|
|
478
185
|
{"Vary": "Accept", **get_headers(InertiaHeaderType(enabled=True))},
|
|
479
186
|
)
|
|
480
|
-
shared_props = get_shared_props(
|
|
187
|
+
shared_props = get_shared_props(
|
|
188
|
+
request,
|
|
189
|
+
partial_data=partial_keys if is_partial_render else None,
|
|
190
|
+
)
|
|
481
191
|
if is_or_contains_lazy_prop(self.content):
|
|
482
|
-
filtered_content = lazy_render(
|
|
192
|
+
filtered_content = lazy_render(
|
|
193
|
+
self.content,
|
|
194
|
+
partial_keys if is_partial_render else None,
|
|
195
|
+
inertia_plugin.portal,
|
|
196
|
+
)
|
|
483
197
|
if filtered_content is not None:
|
|
484
198
|
shared_props["content"] = filtered_content
|
|
485
199
|
elif should_render(self.content, partial_keys):
|
|
@@ -526,7 +240,6 @@ class InertiaResponse(Response[T]):
|
|
|
526
240
|
if self.template_str is not None:
|
|
527
241
|
body = template_engine.render_string(self.template_str, context).encode(self.encoding) # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
|
|
528
242
|
else:
|
|
529
|
-
inertia_plugin = cast("InertiaPlugin", request.app.plugins.get("InertiaPlugin"))
|
|
530
243
|
template_name = self.template_name or inertia_plugin.config.root_template
|
|
531
244
|
template = template_engine.get_template(template_name) # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
|
|
532
245
|
body = template.render(**context).encode(self.encoding) # pyright: ignore[reportUnknownVariableType,reportUnknownMemberType]
|
litestar_vite/plugin.py
CHANGED
|
@@ -6,12 +6,12 @@ import signal
|
|
|
6
6
|
import subprocess
|
|
7
7
|
import threading
|
|
8
8
|
from contextlib import contextmanager
|
|
9
|
+
from dataclasses import dataclass
|
|
9
10
|
from pathlib import Path
|
|
10
|
-
from typing import TYPE_CHECKING, Iterator,
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Iterator, Sequence
|
|
11
12
|
|
|
12
13
|
from litestar.cli._utils import console
|
|
13
14
|
from litestar.contrib.jinja import JinjaTemplateEngine
|
|
14
|
-
from litestar.exceptions import ImproperlyConfiguredException
|
|
15
15
|
from litestar.plugins import CLIPlugin, InitPluginProtocol
|
|
16
16
|
from litestar.static_files import create_static_files_router # pyright: ignore[reportUnknownVariableType]
|
|
17
17
|
|
|
@@ -19,6 +19,16 @@ if TYPE_CHECKING:
|
|
|
19
19
|
from click import Group
|
|
20
20
|
from litestar import Litestar
|
|
21
21
|
from litestar.config.app import AppConfig
|
|
22
|
+
from litestar.datastructures import CacheControlHeader
|
|
23
|
+
from litestar.openapi.spec import SecurityRequirement
|
|
24
|
+
from litestar.types import (
|
|
25
|
+
AfterRequestHookHandler, # pyright: ignore[reportUnknownVariableType]
|
|
26
|
+
AfterResponseHookHandler, # pyright: ignore[reportUnknownVariableType]
|
|
27
|
+
BeforeRequestHookHandler, # pyright: ignore[reportUnknownVariableType]
|
|
28
|
+
ExceptionHandlersMap,
|
|
29
|
+
Guard, # pyright: ignore[reportUnknownVariableType]
|
|
30
|
+
Middleware,
|
|
31
|
+
)
|
|
22
32
|
|
|
23
33
|
from litestar_vite.config import ViteConfig
|
|
24
34
|
from litestar_vite.loader import ViteAssetLoader
|
|
@@ -36,6 +46,20 @@ def set_environment(config: ViteConfig) -> None:
|
|
|
36
46
|
os.environ.setdefault("VITE_DEV_MODE", str(config.dev_mode))
|
|
37
47
|
|
|
38
48
|
|
|
49
|
+
@dataclass
|
|
50
|
+
class StaticFilesConfig:
|
|
51
|
+
after_request: AfterRequestHookHandler | None = None
|
|
52
|
+
after_response: AfterResponseHookHandler | None = None
|
|
53
|
+
before_request: BeforeRequestHookHandler | None = None
|
|
54
|
+
cache_control: CacheControlHeader | None = None
|
|
55
|
+
exception_handlers: ExceptionHandlersMap | None = None
|
|
56
|
+
guards: list[Guard] | None = None
|
|
57
|
+
middleware: Sequence[Middleware] | None = None
|
|
58
|
+
opt: dict[str, Any] | None = None
|
|
59
|
+
security: Sequence[SecurityRequirement] | None = None
|
|
60
|
+
tags: Sequence[str] | None = None
|
|
61
|
+
|
|
62
|
+
|
|
39
63
|
class ViteProcess:
|
|
40
64
|
"""Manages the Vite process."""
|
|
41
65
|
|
|
@@ -86,14 +110,20 @@ class ViteProcess:
|
|
|
86
110
|
class VitePlugin(InitPluginProtocol, CLIPlugin):
|
|
87
111
|
"""Vite plugin."""
|
|
88
112
|
|
|
89
|
-
__slots__ = ("_asset_loader", "_config", "_vite_process")
|
|
113
|
+
__slots__ = ("_asset_loader", "_config", "_static_files_config", "_vite_process")
|
|
90
114
|
|
|
91
|
-
def __init__(
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
config: ViteConfig | None = None,
|
|
118
|
+
asset_loader: ViteAssetLoader | None = None,
|
|
119
|
+
static_files_config: StaticFilesConfig | None = None,
|
|
120
|
+
) -> None:
|
|
92
121
|
"""Initialize ``Vite``.
|
|
93
122
|
|
|
94
123
|
Args:
|
|
95
124
|
config: configuration to use for starting Vite. The default configuration will be used if it is not provided.
|
|
96
125
|
asset_loader: an initialized asset loader to use for rendering asset tags.
|
|
126
|
+
static_files_config: optional configuration dictionary for the static files router.
|
|
97
127
|
"""
|
|
98
128
|
from litestar_vite.config import ViteConfig
|
|
99
129
|
|
|
@@ -102,6 +132,7 @@ class VitePlugin(InitPluginProtocol, CLIPlugin):
|
|
|
102
132
|
self._config = config
|
|
103
133
|
self._asset_loader = asset_loader
|
|
104
134
|
self._vite_process = ViteProcess()
|
|
135
|
+
self._static_files_config: dict[str, Any] = static_files_config.__dict__ if static_files_config else {}
|
|
105
136
|
|
|
106
137
|
@property
|
|
107
138
|
def config(self) -> ViteConfig:
|
|
@@ -128,37 +159,29 @@ class VitePlugin(InitPluginProtocol, CLIPlugin):
|
|
|
128
159
|
"""
|
|
129
160
|
from litestar_vite.loader import render_asset_tag, render_hmr_client
|
|
130
161
|
|
|
131
|
-
if app_config.template_config
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
)
|
|
141
|
-
app_config.template_config.engine_instance.register_template_callable( # pyright: ignore[reportUnknownMemberType]
|
|
142
|
-
key="vite",
|
|
143
|
-
template_callable=render_asset_tag,
|
|
144
|
-
)
|
|
162
|
+
if app_config.template_config and isinstance(app_config.template_config.engine_instance, JinjaTemplateEngine): # pyright: ignore[reportUnknownMemberType]
|
|
163
|
+
app_config.template_config.engine_instance.register_template_callable( # pyright: ignore[reportUnknownMemberType]
|
|
164
|
+
key="vite_hmr",
|
|
165
|
+
template_callable=render_hmr_client,
|
|
166
|
+
)
|
|
167
|
+
app_config.template_config.engine_instance.register_template_callable( # pyright: ignore[reportUnknownMemberType]
|
|
168
|
+
key="vite",
|
|
169
|
+
template_callable=render_asset_tag,
|
|
170
|
+
)
|
|
145
171
|
if self._config.set_static_folders:
|
|
146
172
|
static_dirs = [Path(self._config.bundle_dir), Path(self._config.resource_dir)]
|
|
147
173
|
if Path(self._config.public_dir).exists() and self._config.public_dir != self._config.bundle_dir:
|
|
148
174
|
static_dirs.append(Path(self._config.public_dir))
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
opt={"exclude_from_auth": True},
|
|
160
|
-
),
|
|
161
|
-
)
|
|
175
|
+
base_config = {
|
|
176
|
+
"directories": static_dirs if self._config.dev_mode else [Path(self._config.bundle_dir)],
|
|
177
|
+
"path": self._config.asset_url,
|
|
178
|
+
"name": "vite",
|
|
179
|
+
"html_mode": False,
|
|
180
|
+
"include_in_schema": False,
|
|
181
|
+
"opt": {"exclude_from_auth": True},
|
|
182
|
+
}
|
|
183
|
+
static_files_config: dict[str, Any] = {**base_config, **self._static_files_config}
|
|
184
|
+
app_config.route_handlers.append(create_static_files_router(**static_files_config))
|
|
162
185
|
return app_config
|
|
163
186
|
|
|
164
187
|
@contextmanager
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
litestar_vite/__init__.py,sha256=OioNGhH88mdivQlFz9JlbJV8R6wyjSYE3c8C-RIM4Ls,277
|
|
2
2
|
litestar_vite/__metadata__.py,sha256=_Wo-vNQuj5co9J4FwJAB2rRafbFo8ztTHrXmEPrYrV8,514
|
|
3
3
|
litestar_vite/cli.py,sha256=CBSRohDLU9cDeKMAfSbFiw1x8OE_b15ZlUaxji9Rdw8,10749
|
|
4
|
-
litestar_vite/commands.py,sha256=
|
|
4
|
+
litestar_vite/commands.py,sha256=NFRTA_VoeFuZVk6bOINJTdP9DGGSAIZRVsM-SlDykNk,5228
|
|
5
5
|
litestar_vite/config.py,sha256=cZWIwTwNnBYScCty8OxxPaOL8cELx57dm7JQeV8og3Y,4565
|
|
6
6
|
litestar_vite/loader.py,sha256=nrXL2txXoBZEsdLZnysgBYZSreMXQ7ckLuNcu7MqnSM,10277
|
|
7
|
-
litestar_vite/plugin.py,sha256=
|
|
7
|
+
litestar_vite/plugin.py,sha256=nglizc45_CBG1gqZRDxyGo8cc_KZ1yOJfAS0XiSadpg,9119
|
|
8
8
|
litestar_vite/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
litestar_vite/inertia/__init__.py,sha256=
|
|
9
|
+
litestar_vite/inertia/__init__.py,sha256=KGvxCZhnOw06Pqx5_qjUxj0WWsCR3BR0cVnuNMT7sKQ,1136
|
|
10
10
|
litestar_vite/inertia/_utils.py,sha256=ijO9Lgka7ZPIAHkby9szbTGoSg0nDShC2bqWT9cDxi0,1956
|
|
11
11
|
litestar_vite/inertia/config.py,sha256=0Je9SLg0acv0eRvudk3aJLj5k1DjPxULoVOwAfpjnUc,1232
|
|
12
|
-
litestar_vite/inertia/exception_handler.py,sha256=
|
|
12
|
+
litestar_vite/inertia/exception_handler.py,sha256=BU7vOK7C2iW52_J5xGJZMjYX3EqIg1OpAF3PgbaLSZ4,5349
|
|
13
|
+
litestar_vite/inertia/helpers.py,sha256=9XVQUAqmiXOTUGVLgxPWPgftkRCx99jr6LXPiD35YJE,10571
|
|
13
14
|
litestar_vite/inertia/middleware.py,sha256=23HfQ8D2wNGXUXt_isuGfZ8AFKrr1d_498qGFLynocs,1650
|
|
14
|
-
litestar_vite/inertia/plugin.py,sha256=
|
|
15
|
+
litestar_vite/inertia/plugin.py,sha256=iVF1c8E7M6IdZK_S1nZW8ZTi7vfIllMAnIUX0pVdrFQ,3686
|
|
15
16
|
litestar_vite/inertia/request.py,sha256=Ogt_ikauWrsgKafaip7IL1YhbybwjdBAQ0PQS7cImoQ,3848
|
|
16
|
-
litestar_vite/inertia/response.py,sha256=
|
|
17
|
+
litestar_vite/inertia/response.py,sha256=YjfabkYkGxDwbuo-WLgbUAHcCi0jgZYM_qdnUbI6Pas,13767
|
|
17
18
|
litestar_vite/inertia/routes.py,sha256=QksJm2RUfL-WbuhOieYnPXXWO5GYnPtmsYEm6Ef8Yeo,1782
|
|
18
19
|
litestar_vite/inertia/types.py,sha256=tLp0pm1N__hcWC875khf6wH1nuFlKS9-VjDqgsRkXnw,702
|
|
19
20
|
litestar_vite/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -23,7 +24,7 @@ litestar_vite/templates/package.json.j2,sha256=0JWgdTuaSZ25EmCltF_zbqDdpxfvCLeYu
|
|
|
23
24
|
litestar_vite/templates/styles.css.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
25
|
litestar_vite/templates/tsconfig.json.j2,sha256=q1REIuVyXUHCy4Zi2kgTkmrhdT98vyY89k-WTrImOj8,843
|
|
25
26
|
litestar_vite/templates/vite.config.ts.j2,sha256=bF5kOPFafYMkhhV0VkIwetN-_zoVMGVM1jEMX_wKoNc,1037
|
|
26
|
-
litestar_vite-0.
|
|
27
|
-
litestar_vite-0.
|
|
28
|
-
litestar_vite-0.
|
|
29
|
-
litestar_vite-0.
|
|
27
|
+
litestar_vite-0.13.0.dist-info/METADATA,sha256=qxVk_WT5C6YZaPsDHfPaoauTTXJQh9dL_6BUCVrUeCY,6244
|
|
28
|
+
litestar_vite-0.13.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
29
|
+
litestar_vite-0.13.0.dist-info/licenses/LICENSE,sha256=HeTiEfEgvroUXZe_xAmYHxtTBgw--mbXyZLsWDYabHc,1069
|
|
30
|
+
litestar_vite-0.13.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|