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.
Files changed (55) hide show
  1. litestar_vite/_codegen/__init__.py +26 -0
  2. litestar_vite/_codegen/inertia.py +407 -0
  3. litestar_vite/{codegen/_openapi.py → _codegen/openapi.py} +11 -58
  4. litestar_vite/{codegen/_routes.py → _codegen/routes.py} +43 -110
  5. litestar_vite/{codegen/_ts.py → _codegen/ts.py} +19 -19
  6. litestar_vite/_handler/__init__.py +8 -0
  7. litestar_vite/{handler/_app.py → _handler/app.py} +29 -117
  8. litestar_vite/cli.py +254 -155
  9. litestar_vite/codegen.py +39 -0
  10. litestar_vite/commands.py +6 -0
  11. litestar_vite/{config/__init__.py → config.py} +726 -99
  12. litestar_vite/deploy.py +3 -14
  13. litestar_vite/doctor.py +6 -8
  14. litestar_vite/executor.py +1 -45
  15. litestar_vite/handler.py +9 -0
  16. litestar_vite/html_transform.py +5 -148
  17. litestar_vite/inertia/__init__.py +0 -24
  18. litestar_vite/inertia/_utils.py +0 -5
  19. litestar_vite/inertia/exception_handler.py +16 -22
  20. litestar_vite/inertia/helpers.py +18 -546
  21. litestar_vite/inertia/plugin.py +11 -77
  22. litestar_vite/inertia/request.py +0 -48
  23. litestar_vite/inertia/response.py +17 -113
  24. litestar_vite/inertia/types.py +0 -19
  25. litestar_vite/loader.py +7 -7
  26. litestar_vite/plugin.py +2184 -0
  27. litestar_vite/templates/angular/package.json.j2 +1 -2
  28. litestar_vite/templates/angular-cli/package.json.j2 +1 -2
  29. litestar_vite/templates/base/package.json.j2 +1 -2
  30. litestar_vite/templates/react-inertia/package.json.j2 +1 -2
  31. litestar_vite/templates/vue-inertia/package.json.j2 +1 -2
  32. {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/METADATA +5 -5
  33. {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/RECORD +36 -49
  34. litestar_vite/codegen/__init__.py +0 -48
  35. litestar_vite/codegen/_export.py +0 -229
  36. litestar_vite/codegen/_inertia.py +0 -619
  37. litestar_vite/codegen/_utils.py +0 -141
  38. litestar_vite/config/_constants.py +0 -97
  39. litestar_vite/config/_deploy.py +0 -70
  40. litestar_vite/config/_inertia.py +0 -241
  41. litestar_vite/config/_paths.py +0 -63
  42. litestar_vite/config/_runtime.py +0 -235
  43. litestar_vite/config/_spa.py +0 -93
  44. litestar_vite/config/_types.py +0 -94
  45. litestar_vite/handler/__init__.py +0 -9
  46. litestar_vite/inertia/precognition.py +0 -274
  47. litestar_vite/plugin/__init__.py +0 -687
  48. litestar_vite/plugin/_process.py +0 -185
  49. litestar_vite/plugin/_proxy.py +0 -689
  50. litestar_vite/plugin/_proxy_headers.py +0 -244
  51. litestar_vite/plugin/_static.py +0 -37
  52. litestar_vite/plugin/_utils.py +0 -489
  53. /litestar_vite/{handler/_routing.py → _handler/routing.py} +0 -0
  54. {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/WHEEL +0 -0
  55. {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/licenses/LICENSE +0 -0
@@ -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 lazy property (StaticProp or DeferredProp).
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 lazy property (StaticProp or DeferredProp)
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 extract_once_props(props: "dict[str, Any]") -> "list[str]":
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 is_or_contains_special_prop(value: "Any") -> "bool":
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. Includes a special ``_once_props`` key (list of prop keys
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
- # Track once props for protocol metadata
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") -> "bool":
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 value: session not accessible (user may be unauthenticated)."
1276
- connection.logger.debug(msg)
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") -> "bool":
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 (usually a field name).
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: session not accessible (user may be unauthenticated)."
1301
- connection.logger.debug(msg)
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") -> "bool":
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: session not accessible (user may be unauthenticated)."
1341
- connection.logger.debug(msg)
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: