litestar-vite 0.15.0__py3-none-any.whl → 0.15.0rc2__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.
- litestar_vite/_codegen/__init__.py +26 -0
- litestar_vite/_codegen/inertia.py +407 -0
- litestar_vite/{codegen/_openapi.py → _codegen/openapi.py} +11 -58
- litestar_vite/{codegen/_routes.py → _codegen/routes.py} +43 -110
- litestar_vite/{codegen/_ts.py → _codegen/ts.py} +19 -19
- litestar_vite/_handler/__init__.py +8 -0
- litestar_vite/{handler/_app.py → _handler/app.py} +29 -117
- litestar_vite/cli.py +254 -155
- litestar_vite/codegen.py +39 -0
- litestar_vite/commands.py +6 -0
- litestar_vite/{config/__init__.py → config.py} +726 -99
- litestar_vite/deploy.py +3 -14
- litestar_vite/doctor.py +6 -8
- litestar_vite/executor.py +1 -45
- litestar_vite/handler.py +9 -0
- litestar_vite/html_transform.py +5 -148
- litestar_vite/inertia/__init__.py +0 -24
- litestar_vite/inertia/_utils.py +0 -5
- litestar_vite/inertia/exception_handler.py +16 -22
- litestar_vite/inertia/helpers.py +18 -546
- litestar_vite/inertia/plugin.py +11 -77
- litestar_vite/inertia/request.py +0 -48
- litestar_vite/inertia/response.py +17 -113
- litestar_vite/inertia/types.py +0 -19
- litestar_vite/loader.py +7 -7
- litestar_vite/plugin.py +2184 -0
- litestar_vite/templates/angular/package.json.j2 +1 -2
- litestar_vite/templates/angular-cli/package.json.j2 +1 -2
- litestar_vite/templates/base/package.json.j2 +1 -2
- litestar_vite/templates/react-inertia/package.json.j2 +1 -2
- litestar_vite/templates/vue-inertia/package.json.j2 +1 -2
- {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/METADATA +5 -5
- {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/RECORD +36 -49
- litestar_vite/codegen/__init__.py +0 -48
- litestar_vite/codegen/_export.py +0 -229
- litestar_vite/codegen/_inertia.py +0 -619
- litestar_vite/codegen/_utils.py +0 -141
- litestar_vite/config/_constants.py +0 -97
- litestar_vite/config/_deploy.py +0 -70
- litestar_vite/config/_inertia.py +0 -241
- litestar_vite/config/_paths.py +0 -63
- litestar_vite/config/_runtime.py +0 -235
- litestar_vite/config/_spa.py +0 -93
- litestar_vite/config/_types.py +0 -94
- litestar_vite/handler/__init__.py +0 -9
- litestar_vite/inertia/precognition.py +0 -274
- litestar_vite/plugin/__init__.py +0 -687
- litestar_vite/plugin/_process.py +0 -185
- litestar_vite/plugin/_proxy.py +0 -689
- litestar_vite/plugin/_proxy_headers.py +0 -244
- litestar_vite/plugin/_static.py +0 -37
- litestar_vite/plugin/_utils.py +0 -489
- /litestar_vite/{handler/_routing.py → _handler/routing.py} +0 -0
- {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/WHEEL +0 -0
- {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/licenses/LICENSE +0 -0
litestar_vite/inertia/helpers.py
CHANGED
|
@@ -9,6 +9,7 @@ from anyio.from_thread import BlockingPortal, start_blocking_portal
|
|
|
9
9
|
from litestar.exceptions import ImproperlyConfiguredException
|
|
10
10
|
from litestar.utils.empty import value_or_default
|
|
11
11
|
from litestar.utils.scope.state import ScopeState
|
|
12
|
+
from typing_extensions import ParamSpec
|
|
12
13
|
|
|
13
14
|
from litestar_vite.inertia.types import ScrollPropsConfig
|
|
14
15
|
|
|
@@ -17,6 +18,7 @@ if TYPE_CHECKING:
|
|
|
17
18
|
|
|
18
19
|
from litestar_vite.inertia.plugin import InertiaPlugin
|
|
19
20
|
T = TypeVar("T")
|
|
21
|
+
T_ParamSpec = ParamSpec("T_ParamSpec")
|
|
20
22
|
PropKeyT = TypeVar("PropKeyT", bound=str)
|
|
21
23
|
StaticT = TypeVar("StaticT", bound=object)
|
|
22
24
|
|
|
@@ -144,145 +146,10 @@ def defer(
|
|
|
144
146
|
|
|
145
147
|
defer("teams", lambda: Team.all(), group="attributes")
|
|
146
148
|
defer("projects", lambda: Project.all(), group="attributes")
|
|
147
|
-
|
|
148
|
-
# Chain with .once() for lazy + cached behavior
|
|
149
|
-
defer("stats", lambda: compute_expensive_stats()).once()
|
|
150
149
|
"""
|
|
151
150
|
return DeferredProp[str, T](key=key, value=callback, group=group)
|
|
152
151
|
|
|
153
152
|
|
|
154
|
-
def once(key: str, value_or_callable: "T | Callable[..., T | Coroutine[Any, Any, T]]") -> "OnceProp[str, T]":
|
|
155
|
-
"""Create a prop that resolves once and is cached client-side (v2.2.20+ feature).
|
|
156
|
-
|
|
157
|
-
Once props are included in the initial page load and resolved immediately.
|
|
158
|
-
After resolution, the client caches the value and won't request it again
|
|
159
|
-
on subsequent page visits, unless explicitly requested via partial reload.
|
|
160
|
-
|
|
161
|
-
This is useful for:
|
|
162
|
-
- Expensive computations that rarely change
|
|
163
|
-
- User preferences or settings
|
|
164
|
-
- Feature flags
|
|
165
|
-
- Static configuration
|
|
166
|
-
|
|
167
|
-
Unlike lazy props, once props ARE included in initial loads.
|
|
168
|
-
The "once" behavior tells the client to cache the result.
|
|
169
|
-
|
|
170
|
-
Args:
|
|
171
|
-
key: The key to store the value under.
|
|
172
|
-
value_or_callable: Either a static value or a callable that returns the value.
|
|
173
|
-
|
|
174
|
-
Returns:
|
|
175
|
-
An OnceProp instance.
|
|
176
|
-
|
|
177
|
-
Example::
|
|
178
|
-
|
|
179
|
-
from litestar_vite.inertia import once, InertiaResponse
|
|
180
|
-
|
|
181
|
-
@get("/dashboard", component="Dashboard")
|
|
182
|
-
async def dashboard() -> InertiaResponse:
|
|
183
|
-
return InertiaResponse({
|
|
184
|
-
"user": current_user,
|
|
185
|
-
"settings": once("settings", lambda: Settings.for_user(user_id)),
|
|
186
|
-
"feature_flags": once("feature_flags", get_feature_flags()),
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
See Also:
|
|
190
|
-
- :func:`defer`: For deferred props that support ``.once()`` chaining
|
|
191
|
-
- Inertia.js once props: https://inertiajs.com/partial-reloads#once
|
|
192
|
-
"""
|
|
193
|
-
return OnceProp[str, T](key=key, value=value_or_callable)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
def optional(key: str, callback: "Callable[..., T | Coroutine[Any, Any, T]]") -> "OptionalProp[str, T]":
|
|
197
|
-
"""Create a prop only included when explicitly requested (v2 feature).
|
|
198
|
-
|
|
199
|
-
Optional props are NEVER included in initial page loads or standard
|
|
200
|
-
partial reloads. They're only sent when the client explicitly requests
|
|
201
|
-
them via ``only: ['prop_name']`` in a partial reload.
|
|
202
|
-
|
|
203
|
-
This is designed for use with Inertia's WhenVisible component, which
|
|
204
|
-
triggers a partial reload requesting specific props when an element
|
|
205
|
-
becomes visible in the viewport.
|
|
206
|
-
|
|
207
|
-
The callback is only evaluated when requested, providing both
|
|
208
|
-
bandwidth and CPU optimization.
|
|
209
|
-
|
|
210
|
-
Args:
|
|
211
|
-
key: The key to store the value under.
|
|
212
|
-
callback: A callable (sync or async) that returns the value.
|
|
213
|
-
|
|
214
|
-
Returns:
|
|
215
|
-
An OptionalProp instance.
|
|
216
|
-
|
|
217
|
-
Example::
|
|
218
|
-
|
|
219
|
-
from litestar_vite.inertia import optional, InertiaResponse
|
|
220
|
-
|
|
221
|
-
@get("/posts/{post_id}", component="Posts/Show")
|
|
222
|
-
async def show_post(post_id: int) -> InertiaResponse:
|
|
223
|
-
post = await Post.get(post_id)
|
|
224
|
-
return InertiaResponse({
|
|
225
|
-
"post": post,
|
|
226
|
-
# Only loaded when WhenVisible triggers
|
|
227
|
-
"comments": optional("comments", lambda: Comment.for_post(post_id)),
|
|
228
|
-
"related_posts": optional("related_posts", lambda: Post.related(post_id)),
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
Frontend usage with WhenVisible::
|
|
232
|
-
|
|
233
|
-
<WhenVisible data="comments" :params="{ only: ['comments'] }">
|
|
234
|
-
<template #fallback>
|
|
235
|
-
<LoadingSpinner />
|
|
236
|
-
</template>
|
|
237
|
-
<CommentList :comments="comments" />
|
|
238
|
-
</WhenVisible>
|
|
239
|
-
|
|
240
|
-
See Also:
|
|
241
|
-
- Inertia.js WhenVisible: https://inertiajs.com/load-when-visible
|
|
242
|
-
"""
|
|
243
|
-
return OptionalProp[str, T](key=key, callback=callback)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
def always(key: str, value: "T") -> "AlwaysProp[str, T]":
|
|
247
|
-
"""Create a prop always included, even during partial reloads (v2 feature).
|
|
248
|
-
|
|
249
|
-
Always props bypass partial reload filtering entirely. They're included
|
|
250
|
-
in every response regardless of what keys the client requests.
|
|
251
|
-
|
|
252
|
-
Use for critical data that must always be present:
|
|
253
|
-
- Authentication state
|
|
254
|
-
- Permission flags
|
|
255
|
-
- Feature toggles
|
|
256
|
-
- Error states
|
|
257
|
-
|
|
258
|
-
Args:
|
|
259
|
-
key: The key to store the value under.
|
|
260
|
-
value: The value (evaluated eagerly).
|
|
261
|
-
|
|
262
|
-
Returns:
|
|
263
|
-
An AlwaysProp instance.
|
|
264
|
-
|
|
265
|
-
Example::
|
|
266
|
-
|
|
267
|
-
from litestar_vite.inertia import always, lazy, InertiaResponse
|
|
268
|
-
|
|
269
|
-
@get("/dashboard", component="Dashboard")
|
|
270
|
-
async def dashboard(request: Request) -> InertiaResponse:
|
|
271
|
-
return InertiaResponse({
|
|
272
|
-
# Always sent, even during partial reloads for other props
|
|
273
|
-
"auth": always("auth", {"user": request.user, "can": permissions}),
|
|
274
|
-
# Only sent when explicitly requested
|
|
275
|
-
"analytics": lazy("analytics", get_analytics),
|
|
276
|
-
"reports": lazy("reports", get_reports),
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
See Also:
|
|
280
|
-
- :func:`lazy`: For props excluded from initial load
|
|
281
|
-
- :func:`optional`: For props only included when explicitly requested
|
|
282
|
-
"""
|
|
283
|
-
return AlwaysProp[str, T](key=key, value=value)
|
|
284
|
-
|
|
285
|
-
|
|
286
153
|
@dataclass
|
|
287
154
|
class PropFilter:
|
|
288
155
|
"""Configuration for prop filtering during partial reloads.
|
|
@@ -598,12 +465,10 @@ class DeferredProp(Generic[PropKeyT, T]):
|
|
|
598
465
|
key: "PropKeyT",
|
|
599
466
|
value: "Callable[..., T | Coroutine[Any, Any, T] | None] | None" = None,
|
|
600
467
|
group: str = DEFAULT_DEFERRED_GROUP,
|
|
601
|
-
is_once: bool = False,
|
|
602
468
|
) -> None:
|
|
603
469
|
self._key = key
|
|
604
470
|
self._value = value
|
|
605
471
|
self._group = group
|
|
606
|
-
self._is_once = is_once
|
|
607
472
|
self._evaluated = False
|
|
608
473
|
self._result: "T | None" = None
|
|
609
474
|
|
|
@@ -620,32 +485,6 @@ class DeferredProp(Generic[PropKeyT, T]):
|
|
|
620
485
|
def key(self) -> "PropKeyT":
|
|
621
486
|
return self._key
|
|
622
487
|
|
|
623
|
-
@property
|
|
624
|
-
def is_once(self) -> bool:
|
|
625
|
-
"""Whether this prop should only be resolved once and cached client-side.
|
|
626
|
-
|
|
627
|
-
Returns:
|
|
628
|
-
True if this is a once prop.
|
|
629
|
-
"""
|
|
630
|
-
return self._is_once
|
|
631
|
-
|
|
632
|
-
def once(self) -> "DeferredProp[PropKeyT, T]":
|
|
633
|
-
"""Return a new DeferredProp with once behavior enabled.
|
|
634
|
-
|
|
635
|
-
Once props are cached client-side after first resolution.
|
|
636
|
-
They won't be re-fetched on subsequent visits unless explicitly
|
|
637
|
-
requested via partial reload.
|
|
638
|
-
|
|
639
|
-
Returns:
|
|
640
|
-
A new DeferredProp with is_once=True.
|
|
641
|
-
|
|
642
|
-
Example::
|
|
643
|
-
|
|
644
|
-
# Combine defer with once for lazy + cached behavior
|
|
645
|
-
defer("stats", lambda: compute_expensive_stats()).once()
|
|
646
|
-
"""
|
|
647
|
-
return DeferredProp[PropKeyT, T](key=self._key, value=self._value, group=self._group, is_once=True)
|
|
648
|
-
|
|
649
488
|
@staticmethod
|
|
650
489
|
@contextmanager
|
|
651
490
|
def with_portal(portal: "BlockingPortal | None" = None) -> "Generator[BlockingPortal, None, None]":
|
|
@@ -676,252 +515,18 @@ class DeferredProp(Generic[PropKeyT, T]):
|
|
|
676
515
|
return self._result
|
|
677
516
|
|
|
678
517
|
|
|
679
|
-
class OnceProp(Generic[PropKeyT, T]):
|
|
680
|
-
"""A wrapper for once-only property evaluation (v2.2.20+ feature).
|
|
681
|
-
|
|
682
|
-
Once props are resolved once and cached client-side. They won't be
|
|
683
|
-
re-fetched on subsequent page visits unless explicitly requested
|
|
684
|
-
via partial reload with ``only: ['key']``.
|
|
685
|
-
|
|
686
|
-
This is useful for expensive computations that rarely change
|
|
687
|
-
(e.g., user preferences, feature flags, static configuration).
|
|
688
|
-
|
|
689
|
-
Unlike lazy props, once props ARE included in the initial page load.
|
|
690
|
-
The "once" behavior tells the client to cache the value and not
|
|
691
|
-
request it again on future visits.
|
|
692
|
-
"""
|
|
693
|
-
|
|
694
|
-
def __init__(self, key: "PropKeyT", value: "T | Callable[..., T | Coroutine[Any, Any, T]]") -> None:
|
|
695
|
-
"""Initialize a OnceProp.
|
|
696
|
-
|
|
697
|
-
Args:
|
|
698
|
-
key: The prop key.
|
|
699
|
-
value: Either a static value or a callable that returns the value.
|
|
700
|
-
"""
|
|
701
|
-
self._key = key
|
|
702
|
-
self._value = value
|
|
703
|
-
self._evaluated = False
|
|
704
|
-
self._result: "T | None" = None
|
|
705
|
-
|
|
706
|
-
@property
|
|
707
|
-
def key(self) -> "PropKeyT":
|
|
708
|
-
return self._key
|
|
709
|
-
|
|
710
|
-
@staticmethod
|
|
711
|
-
@contextmanager
|
|
712
|
-
def with_portal(portal: "BlockingPortal | None" = None) -> "Generator[BlockingPortal, None, None]":
|
|
713
|
-
if portal is None:
|
|
714
|
-
with start_blocking_portal() as p:
|
|
715
|
-
yield p
|
|
716
|
-
else:
|
|
717
|
-
yield portal
|
|
718
|
-
|
|
719
|
-
@staticmethod
|
|
720
|
-
def _is_awaitable(v: "Callable[..., T | Coroutine[Any, Any, T]]") -> "TypeGuard[Coroutine[Any, Any, T]]":
|
|
721
|
-
return inspect.iscoroutinefunction(v)
|
|
722
|
-
|
|
723
|
-
def render(self, portal: "BlockingPortal | None" = None) -> "T | None":
|
|
724
|
-
"""Render the prop value, caching the result.
|
|
725
|
-
|
|
726
|
-
Args:
|
|
727
|
-
portal: Optional blocking portal for async callbacks.
|
|
728
|
-
|
|
729
|
-
Returns:
|
|
730
|
-
The rendered value.
|
|
731
|
-
"""
|
|
732
|
-
if self._evaluated:
|
|
733
|
-
return self._result
|
|
734
|
-
if not callable(self._value):
|
|
735
|
-
self._result = self._value
|
|
736
|
-
self._evaluated = True
|
|
737
|
-
return self._result
|
|
738
|
-
if not self._is_awaitable(cast("Callable[..., T]", self._value)):
|
|
739
|
-
self._result = cast("T", self._value())
|
|
740
|
-
self._evaluated = True
|
|
741
|
-
return self._result
|
|
742
|
-
with self.with_portal(portal) as p:
|
|
743
|
-
self._result = p.call(cast("Callable[..., T]", self._value))
|
|
744
|
-
self._evaluated = True
|
|
745
|
-
return self._result
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
class OptionalProp(Generic[PropKeyT, T]):
|
|
749
|
-
"""A wrapper for optional property evaluation (v2 feature).
|
|
750
|
-
|
|
751
|
-
Optional props are NEVER included in initial page loads or standard
|
|
752
|
-
partial reloads. They're only sent when the client explicitly requests
|
|
753
|
-
them via ``only: ['prop_name']``.
|
|
754
|
-
|
|
755
|
-
This is designed for use with Inertia's WhenVisible component, which
|
|
756
|
-
loads data only when an element becomes visible in the viewport.
|
|
757
|
-
|
|
758
|
-
The callback is only evaluated when the prop is explicitly requested,
|
|
759
|
-
providing both bandwidth and CPU optimization.
|
|
760
|
-
"""
|
|
761
|
-
|
|
762
|
-
def __init__(self, key: "PropKeyT", callback: "Callable[..., T | Coroutine[Any, Any, T]]") -> None:
|
|
763
|
-
"""Initialize an OptionalProp.
|
|
764
|
-
|
|
765
|
-
Args:
|
|
766
|
-
key: The prop key.
|
|
767
|
-
callback: A callable that returns the value when requested.
|
|
768
|
-
"""
|
|
769
|
-
self._key = key
|
|
770
|
-
self._callback = callback
|
|
771
|
-
self._evaluated = False
|
|
772
|
-
self._result: "T | None" = None
|
|
773
|
-
|
|
774
|
-
@property
|
|
775
|
-
def key(self) -> "PropKeyT":
|
|
776
|
-
return self._key
|
|
777
|
-
|
|
778
|
-
@staticmethod
|
|
779
|
-
@contextmanager
|
|
780
|
-
def with_portal(portal: "BlockingPortal | None" = None) -> "Generator[BlockingPortal, None, None]":
|
|
781
|
-
if portal is None:
|
|
782
|
-
with start_blocking_portal() as p:
|
|
783
|
-
yield p
|
|
784
|
-
else:
|
|
785
|
-
yield portal
|
|
786
|
-
|
|
787
|
-
@staticmethod
|
|
788
|
-
def _is_awaitable(v: "Callable[..., T | Coroutine[Any, Any, T]]") -> "TypeGuard[Coroutine[Any, Any, T]]":
|
|
789
|
-
return inspect.iscoroutinefunction(v)
|
|
790
|
-
|
|
791
|
-
def render(self, portal: "BlockingPortal | None" = None) -> "T | None":
|
|
792
|
-
"""Render the prop value, caching the result.
|
|
793
|
-
|
|
794
|
-
Args:
|
|
795
|
-
portal: Optional blocking portal for async callbacks.
|
|
796
|
-
|
|
797
|
-
Returns:
|
|
798
|
-
The rendered value.
|
|
799
|
-
"""
|
|
800
|
-
if self._evaluated:
|
|
801
|
-
return self._result
|
|
802
|
-
if not self._is_awaitable(cast("Callable[..., T]", self._callback)):
|
|
803
|
-
self._result = cast("T", self._callback())
|
|
804
|
-
self._evaluated = True
|
|
805
|
-
return self._result
|
|
806
|
-
with self.with_portal(portal) as p:
|
|
807
|
-
self._result = p.call(cast("Callable[..., T]", self._callback))
|
|
808
|
-
self._evaluated = True
|
|
809
|
-
return self._result
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
class AlwaysProp(Generic[PropKeyT, T]):
|
|
813
|
-
"""A wrapper for always-included property evaluation (v2 feature).
|
|
814
|
-
|
|
815
|
-
Always props are ALWAYS included in responses, even during partial
|
|
816
|
-
reloads. This is the opposite of lazy props - they bypass any
|
|
817
|
-
partial reload filtering.
|
|
818
|
-
|
|
819
|
-
Use for critical data that must always be present, such as:
|
|
820
|
-
- Authentication state
|
|
821
|
-
- Permission flags
|
|
822
|
-
- Feature toggles
|
|
823
|
-
- Error states
|
|
824
|
-
"""
|
|
825
|
-
|
|
826
|
-
def __init__(self, key: "PropKeyT", value: "T") -> None:
|
|
827
|
-
"""Initialize an AlwaysProp.
|
|
828
|
-
|
|
829
|
-
Args:
|
|
830
|
-
key: The prop key.
|
|
831
|
-
value: The value (always evaluated eagerly).
|
|
832
|
-
"""
|
|
833
|
-
self._key = key
|
|
834
|
-
self._value = value
|
|
835
|
-
|
|
836
|
-
@property
|
|
837
|
-
def key(self) -> "PropKeyT":
|
|
838
|
-
return self._key
|
|
839
|
-
|
|
840
|
-
@property
|
|
841
|
-
def value(self) -> "T":
|
|
842
|
-
return self._value
|
|
843
|
-
|
|
844
|
-
def render(self, portal: "BlockingPortal | None" = None) -> "T": # pyright: ignore
|
|
845
|
-
"""Return the prop value.
|
|
846
|
-
|
|
847
|
-
Args:
|
|
848
|
-
portal: Unused, included for interface consistency.
|
|
849
|
-
|
|
850
|
-
Returns:
|
|
851
|
-
The prop value.
|
|
852
|
-
"""
|
|
853
|
-
return self._value
|
|
854
|
-
|
|
855
|
-
|
|
856
518
|
def is_lazy_prop(value: "Any") -> "TypeGuard[DeferredProp[Any, Any] | StaticProp[Any, Any]]":
|
|
857
|
-
"""Check if value is a
|
|
858
|
-
|
|
859
|
-
Lazy props are excluded from initial page loads and only sent when
|
|
860
|
-
explicitly requested via partial reload.
|
|
519
|
+
"""Check if value is a deferred property.
|
|
861
520
|
|
|
862
521
|
Args:
|
|
863
522
|
value: Any value to check
|
|
864
523
|
|
|
865
524
|
Returns:
|
|
866
|
-
True if value is a
|
|
525
|
+
True if value is a deferred property
|
|
867
526
|
"""
|
|
868
527
|
return isinstance(value, (DeferredProp, StaticProp))
|
|
869
528
|
|
|
870
529
|
|
|
871
|
-
def is_once_prop(value: "Any") -> "TypeGuard[OnceProp[Any, Any]]":
|
|
872
|
-
"""Check if value is a once prop.
|
|
873
|
-
|
|
874
|
-
Once props are included in initial loads but cached client-side.
|
|
875
|
-
|
|
876
|
-
Args:
|
|
877
|
-
value: Any value to check
|
|
878
|
-
|
|
879
|
-
Returns:
|
|
880
|
-
True if value is an OnceProp
|
|
881
|
-
"""
|
|
882
|
-
return isinstance(value, OnceProp)
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
def is_optional_prop(value: "Any") -> "TypeGuard[OptionalProp[Any, Any]]":
|
|
886
|
-
"""Check if value is an optional prop.
|
|
887
|
-
|
|
888
|
-
Optional props are only included when explicitly requested.
|
|
889
|
-
|
|
890
|
-
Args:
|
|
891
|
-
value: Any value to check
|
|
892
|
-
|
|
893
|
-
Returns:
|
|
894
|
-
True if value is an OptionalProp
|
|
895
|
-
"""
|
|
896
|
-
return isinstance(value, OptionalProp)
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
def is_always_prop(value: "Any") -> "TypeGuard[AlwaysProp[Any, Any]]":
|
|
900
|
-
"""Check if value is an always prop.
|
|
901
|
-
|
|
902
|
-
Always props bypass partial reload filtering.
|
|
903
|
-
|
|
904
|
-
Args:
|
|
905
|
-
value: Any value to check
|
|
906
|
-
|
|
907
|
-
Returns:
|
|
908
|
-
True if value is an AlwaysProp
|
|
909
|
-
"""
|
|
910
|
-
return isinstance(value, AlwaysProp)
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
def is_special_prop(value: "Any") -> bool:
|
|
914
|
-
"""Check if value is any special prop type (lazy, once, optional, always).
|
|
915
|
-
|
|
916
|
-
Args:
|
|
917
|
-
value: Any value to check
|
|
918
|
-
|
|
919
|
-
Returns:
|
|
920
|
-
True if value is a special prop wrapper
|
|
921
|
-
"""
|
|
922
|
-
return isinstance(value, (DeferredProp, StaticProp, OnceProp, OptionalProp, AlwaysProp))
|
|
923
|
-
|
|
924
|
-
|
|
925
530
|
def is_deferred_prop(value: "Any") -> "TypeGuard[DeferredProp[Any, Any]]":
|
|
926
531
|
"""Check if value is specifically a DeferredProp (not StaticProp).
|
|
927
532
|
|
|
@@ -940,9 +545,6 @@ def extract_deferred_props(props: "dict[str, Any]") -> "dict[str, list[str]]":
|
|
|
940
545
|
This extracts all DeferredProp instances from the props dict and groups them
|
|
941
546
|
by their group name, returning a dict mapping group -> list of prop keys.
|
|
942
547
|
|
|
943
|
-
Note: DeferredProp instances with is_once=True are excluded from the result
|
|
944
|
-
because once props should not be re-fetched after initial resolution.
|
|
945
|
-
|
|
946
548
|
Args:
|
|
947
549
|
props: The props dictionary to scan.
|
|
948
550
|
|
|
@@ -966,9 +568,6 @@ def extract_deferred_props(props: "dict[str, Any]") -> "dict[str, list[str]]":
|
|
|
966
568
|
|
|
967
569
|
for key, value in props.items():
|
|
968
570
|
if is_deferred_prop(value):
|
|
969
|
-
# Exclude once props from deferred metadata
|
|
970
|
-
if value.is_once:
|
|
971
|
-
continue
|
|
972
571
|
group = value.group
|
|
973
572
|
if group not in groups:
|
|
974
573
|
groups[group] = []
|
|
@@ -977,40 +576,7 @@ def extract_deferred_props(props: "dict[str, Any]") -> "dict[str, list[str]]":
|
|
|
977
576
|
return groups
|
|
978
577
|
|
|
979
578
|
|
|
980
|
-
def
|
|
981
|
-
"""Extract once props for the Inertia v2.2.20+ protocol.
|
|
982
|
-
|
|
983
|
-
Once props are cached client-side after first resolution. This function
|
|
984
|
-
extracts all OnceProp instances and DeferredProp instances with is_once=True.
|
|
985
|
-
|
|
986
|
-
Args:
|
|
987
|
-
props: The props dictionary to scan.
|
|
988
|
-
|
|
989
|
-
Returns:
|
|
990
|
-
A list of prop keys that should be cached client-side.
|
|
991
|
-
Empty list if no once props found.
|
|
992
|
-
|
|
993
|
-
Example::
|
|
994
|
-
|
|
995
|
-
props = {
|
|
996
|
-
"user": current_user,
|
|
997
|
-
"settings": once("settings", get_settings),
|
|
998
|
-
"stats": defer("stats", get_stats).once(),
|
|
999
|
-
}
|
|
1000
|
-
result = extract_once_props(props)
|
|
1001
|
-
|
|
1002
|
-
The result is ["settings", "stats"].
|
|
1003
|
-
"""
|
|
1004
|
-
once_keys: "list[str]" = []
|
|
1005
|
-
|
|
1006
|
-
for key, value in props.items():
|
|
1007
|
-
if is_once_prop(value) or (is_deferred_prop(value) and value.is_once):
|
|
1008
|
-
once_keys.append(key)
|
|
1009
|
-
|
|
1010
|
-
return once_keys
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
def should_render( # noqa: PLR0911
|
|
579
|
+
def should_render(
|
|
1014
580
|
value: "Any",
|
|
1015
581
|
partial_data: "set[str] | None" = None,
|
|
1016
582
|
partial_except: "set[str] | None" = None,
|
|
@@ -1021,13 +587,6 @@ def should_render( # noqa: PLR0911
|
|
|
1021
587
|
For v2 protocol, partial_except takes precedence over partial_data.
|
|
1022
588
|
When a key is provided, filtering applies to all props (not just lazy props).
|
|
1023
589
|
|
|
1024
|
-
Prop types have different behaviors:
|
|
1025
|
-
- **AlwaysProp**: Always included, bypasses all filtering
|
|
1026
|
-
- **OptionalProp**: Only included when explicitly requested via partial_data
|
|
1027
|
-
- **LazyProp** (StaticProp/DeferredProp): Excluded from initial load, included on partial reload
|
|
1028
|
-
- **OnceProp**: Included in initial load, cached client-side
|
|
1029
|
-
- **Regular values**: Follow standard partial reload filtering
|
|
1030
|
-
|
|
1031
590
|
Args:
|
|
1032
591
|
value: Any value to check
|
|
1033
592
|
partial_data: Optional set of keys to include (X-Inertia-Partial-Data)
|
|
@@ -1037,26 +596,6 @@ def should_render( # noqa: PLR0911
|
|
|
1037
596
|
Returns:
|
|
1038
597
|
bool: True if value should be rendered
|
|
1039
598
|
"""
|
|
1040
|
-
# AlwaysProp: Always render, bypass all filtering
|
|
1041
|
-
if is_always_prop(value):
|
|
1042
|
-
return True
|
|
1043
|
-
|
|
1044
|
-
# OptionalProp: Only render when explicitly requested
|
|
1045
|
-
if is_optional_prop(value):
|
|
1046
|
-
if partial_data:
|
|
1047
|
-
return value.key in partial_data
|
|
1048
|
-
# Never included in initial loads or standard partial reloads
|
|
1049
|
-
return False
|
|
1050
|
-
|
|
1051
|
-
# OnceProp: Always render (client handles caching)
|
|
1052
|
-
if is_once_prop(value):
|
|
1053
|
-
# Once props are always included - the client decides whether to use cached value
|
|
1054
|
-
# However, respect partial_except if specified
|
|
1055
|
-
if partial_except:
|
|
1056
|
-
return value.key not in partial_except
|
|
1057
|
-
return True
|
|
1058
|
-
|
|
1059
|
-
# LazyProp (StaticProp/DeferredProp): Only render on partial reload
|
|
1060
599
|
if is_lazy_prop(value):
|
|
1061
600
|
if partial_except:
|
|
1062
601
|
return value.key not in partial_except
|
|
@@ -1064,7 +603,6 @@ def should_render( # noqa: PLR0911
|
|
|
1064
603
|
return value.key in partial_data
|
|
1065
604
|
return False
|
|
1066
605
|
|
|
1067
|
-
# Regular values: Apply standard filtering
|
|
1068
606
|
if key is not None:
|
|
1069
607
|
if partial_except:
|
|
1070
608
|
return key not in partial_except
|
|
@@ -1094,29 +632,7 @@ def is_or_contains_lazy_prop(value: "Any") -> "bool":
|
|
|
1094
632
|
return False
|
|
1095
633
|
|
|
1096
634
|
|
|
1097
|
-
def
|
|
1098
|
-
"""Check if value is or contains any special prop type.
|
|
1099
|
-
|
|
1100
|
-
This includes lazy, once, optional, and always props.
|
|
1101
|
-
|
|
1102
|
-
Args:
|
|
1103
|
-
value: Any value to check
|
|
1104
|
-
|
|
1105
|
-
Returns:
|
|
1106
|
-
True if value is or contains a special prop
|
|
1107
|
-
"""
|
|
1108
|
-
if is_special_prop(value):
|
|
1109
|
-
return True
|
|
1110
|
-
if isinstance(value, str):
|
|
1111
|
-
return False
|
|
1112
|
-
if isinstance(value, Mapping):
|
|
1113
|
-
return any(is_or_contains_special_prop(v) for v in cast("Mapping[str, Any]", value).values())
|
|
1114
|
-
if isinstance(value, Iterable):
|
|
1115
|
-
return any(is_or_contains_special_prop(v) for v in cast("Iterable[Any]", value))
|
|
1116
|
-
return False
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
def lazy_render( # noqa: PLR0911
|
|
635
|
+
def lazy_render(
|
|
1120
636
|
value: "T",
|
|
1121
637
|
partial_data: "set[str] | None" = None,
|
|
1122
638
|
portal: "BlockingPortal | None" = None,
|
|
@@ -1167,19 +683,9 @@ def lazy_render( # noqa: PLR0911
|
|
|
1167
683
|
),
|
|
1168
684
|
)
|
|
1169
685
|
|
|
1170
|
-
# Handle special prop types that need rendering
|
|
1171
686
|
if is_lazy_prop(value) and should_render(value, partial_data, partial_except):
|
|
1172
687
|
return cast("T", value.render(portal))
|
|
1173
688
|
|
|
1174
|
-
if is_once_prop(value) and should_render(value, partial_data, partial_except):
|
|
1175
|
-
return cast("T", value.render(portal))
|
|
1176
|
-
|
|
1177
|
-
if is_optional_prop(value) and should_render(value, partial_data, partial_except):
|
|
1178
|
-
return cast("T", value.render(portal))
|
|
1179
|
-
|
|
1180
|
-
if is_always_prop(value):
|
|
1181
|
-
return cast("T", value.render(portal))
|
|
1182
|
-
|
|
1183
689
|
return cast("T", value)
|
|
1184
690
|
|
|
1185
691
|
|
|
@@ -1198,8 +704,7 @@ def get_shared_props(
|
|
|
1198
704
|
partial_except: Optional set of keys to exclude (X-Inertia-Partial-Except, v2).
|
|
1199
705
|
|
|
1200
706
|
Returns:
|
|
1201
|
-
The shared props.
|
|
1202
|
-
that were OnceProp instances) for protocol metadata generation.
|
|
707
|
+
The shared props.
|
|
1203
708
|
|
|
1204
709
|
Note:
|
|
1205
710
|
Be sure to call this before `self.create_template_context` if you would like to include the `flash` message details.
|
|
@@ -1207,7 +712,6 @@ def get_shared_props(
|
|
|
1207
712
|
props: "dict[str, Any]" = {}
|
|
1208
713
|
flash: "dict[str, list[str]]" = defaultdict(list)
|
|
1209
714
|
errors: "dict[str, Any]" = {}
|
|
1210
|
-
once_props_keys: "list[str]" = []
|
|
1211
715
|
error_bag = request.headers.get("X-Inertia-Error-Bag", None)
|
|
1212
716
|
|
|
1213
717
|
try:
|
|
@@ -1218,11 +722,7 @@ def get_shared_props(
|
|
|
1218
722
|
for key, value in shared_props.items():
|
|
1219
723
|
if not should_render(value, partial_data, partial_except, key=key):
|
|
1220
724
|
continue
|
|
1221
|
-
|
|
1222
|
-
if is_once_prop(value) or (is_deferred_prop(value) and value.is_once):
|
|
1223
|
-
once_props_keys.append(key)
|
|
1224
|
-
# Render all special prop types
|
|
1225
|
-
if is_special_prop(value):
|
|
725
|
+
if is_lazy_prop(value):
|
|
1226
726
|
props[key] = value.render(inertia_plugin.portal)
|
|
1227
727
|
else:
|
|
1228
728
|
props[key] = value
|
|
@@ -1249,62 +749,40 @@ def get_shared_props(
|
|
|
1249
749
|
props["flash"] = flash
|
|
1250
750
|
props["errors"] = {error_bag: errors} if error_bag is not None else errors
|
|
1251
751
|
props["csrf_token"] = value_or_default(ScopeState.from_scope(request.scope).csrf_token, "")
|
|
1252
|
-
# Store once props keys for later extraction (removed before serialization)
|
|
1253
|
-
props["_once_props"] = once_props_keys
|
|
1254
752
|
return props
|
|
1255
753
|
|
|
1256
754
|
|
|
1257
|
-
def share(connection: "ASGIConnection[Any, Any, Any, Any]", key: "str", value: "Any") -> "
|
|
755
|
+
def share(connection: "ASGIConnection[Any, Any, Any, Any]", key: "str", value: "Any") -> "None":
|
|
1258
756
|
"""Share a value in the session.
|
|
1259
757
|
|
|
1260
|
-
Shared values are included in the props of every Inertia response for
|
|
1261
|
-
the current request. This is useful for data that should be available
|
|
1262
|
-
to all components (e.g., authenticated user, permissions, settings).
|
|
1263
|
-
|
|
1264
758
|
Args:
|
|
1265
759
|
connection: The ASGI connection.
|
|
1266
760
|
key: The key to store the value under.
|
|
1267
761
|
value: The value to store.
|
|
1268
|
-
|
|
1269
|
-
Returns:
|
|
1270
|
-
True if the value was successfully shared, False otherwise.
|
|
1271
762
|
"""
|
|
1272
763
|
try:
|
|
1273
764
|
connection.session.setdefault("_shared", {}).update({key: value})
|
|
1274
765
|
except (AttributeError, ImproperlyConfiguredException):
|
|
1275
|
-
msg = "Unable to share
|
|
1276
|
-
connection.logger.
|
|
1277
|
-
return False
|
|
1278
|
-
else:
|
|
1279
|
-
return True
|
|
766
|
+
msg = "Unable to set `share` session state. A valid session was not found for this request."
|
|
767
|
+
connection.logger.warning(msg)
|
|
1280
768
|
|
|
1281
769
|
|
|
1282
|
-
def error(connection: "ASGIConnection[Any, Any, Any, Any]", key: "str", message: "str") -> "
|
|
770
|
+
def error(connection: "ASGIConnection[Any, Any, Any, Any]", key: "str", message: "str") -> "None":
|
|
1283
771
|
"""Set an error message in the session.
|
|
1284
772
|
|
|
1285
|
-
Error messages are included in the ``errors`` prop of Inertia responses,
|
|
1286
|
-
typically used for form validation errors. The key usually corresponds
|
|
1287
|
-
to a form field name.
|
|
1288
|
-
|
|
1289
773
|
Args:
|
|
1290
774
|
connection: The ASGI connection.
|
|
1291
|
-
key: The key to store the error under
|
|
775
|
+
key: The key to store the error under.
|
|
1292
776
|
message: The error message.
|
|
1293
|
-
|
|
1294
|
-
Returns:
|
|
1295
|
-
True if the error was successfully stored, False otherwise.
|
|
1296
777
|
"""
|
|
1297
778
|
try:
|
|
1298
779
|
connection.session.setdefault("_errors", {}).update({key: message})
|
|
1299
780
|
except (AttributeError, ImproperlyConfiguredException):
|
|
1300
|
-
msg = "Unable to set error
|
|
1301
|
-
connection.logger.
|
|
1302
|
-
return False
|
|
1303
|
-
else:
|
|
1304
|
-
return True
|
|
781
|
+
msg = "Unable to set `error` session state. A valid session was not found for this request."
|
|
782
|
+
connection.logger.warning(msg)
|
|
1305
783
|
|
|
1306
784
|
|
|
1307
|
-
def flash(connection: "ASGIConnection[Any, Any, Any, Any]", message: "str", category: "str" = "info") -> "
|
|
785
|
+
def flash(connection: "ASGIConnection[Any, Any, Any, Any]", message: "str", category: "str" = "info") -> "None":
|
|
1308
786
|
"""Add a flash message to the session.
|
|
1309
787
|
|
|
1310
788
|
Flash messages are stored in the session and passed to the frontend
|
|
@@ -1321,9 +799,6 @@ def flash(connection: "ASGIConnection[Any, Any, Any, Any]", message: "str", cate
|
|
|
1321
799
|
category: The message category (e.g., "success", "error", "warning", "info").
|
|
1322
800
|
Defaults to "info".
|
|
1323
801
|
|
|
1324
|
-
Returns:
|
|
1325
|
-
True if the flash message was successfully stored, False otherwise.
|
|
1326
|
-
|
|
1327
802
|
Example::
|
|
1328
803
|
|
|
1329
804
|
from litestar_vite.inertia import flash
|
|
@@ -1337,11 +812,8 @@ def flash(connection: "ASGIConnection[Any, Any, Any, Any]", message: "str", cate
|
|
|
1337
812
|
messages = connection.session.setdefault("_messages", [])
|
|
1338
813
|
messages.append({"category": category, "message": message})
|
|
1339
814
|
except (AttributeError, ImproperlyConfiguredException):
|
|
1340
|
-
msg = "Unable to flash message
|
|
1341
|
-
connection.logger.
|
|
1342
|
-
return False
|
|
1343
|
-
else:
|
|
1344
|
-
return True
|
|
815
|
+
msg = "Unable to set flash message. A valid session was not found for this request."
|
|
816
|
+
connection.logger.warning(msg)
|
|
1345
817
|
|
|
1346
818
|
|
|
1347
819
|
def clear_history(connection: "ASGIConnection[Any, Any, Any, Any]") -> None:
|