litestar-vite 0.12.0__tar.gz → 0.12.1__tar.gz

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.

Files changed (74) hide show
  1. {litestar_vite-0.12.0 → litestar_vite-0.12.1}/PKG-INFO +1 -1
  2. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/commands.py +4 -4
  3. litestar_vite-0.12.1/litestar_vite/inertia/__init__.py +37 -0
  4. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/inertia/exception_handler.py +2 -1
  5. litestar_vite-0.12.1/litestar_vite/inertia/helpers.py +329 -0
  6. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/inertia/plugin.py +19 -7
  7. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/inertia/response.py +18 -305
  8. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/plugin.py +9 -15
  9. {litestar_vite-0.12.0 → litestar_vite-0.12.1}/pyproject.toml +15 -6
  10. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/conftest.py +7 -10
  11. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_cli/conftest.py +3 -3
  12. litestar_vite-0.12.1/tests/test_inertia/__init__.py +0 -0
  13. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_inertia/conftest.py +0 -2
  14. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_inertia/test_response.py +9 -13
  15. litestar_vite-0.12.0/src/js/LICENSE +0 -45
  16. litestar_vite-0.12.0/src/js/Makefile +0 -66
  17. litestar_vite-0.12.0/src/js/NOTICE +0 -25
  18. litestar_vite-0.12.0/src/js/README.md +0 -15
  19. litestar_vite-0.12.0/src/js/src/dev-server-index.html +0 -185
  20. litestar_vite-0.12.0/src/js/src/index.ts +0 -602
  21. litestar_vite-0.12.0/src/js/src/inertia-helpers/index.ts +0 -170
  22. litestar_vite-0.12.0/src/js/tests/__data__/dummy.ts +0 -1
  23. litestar_vite-0.12.0/src/js/tests/index.test.ts +0 -691
  24. litestar_vite-0.12.0/src/js/tsconfig.inertia-helpers.json +0 -7
  25. litestar_vite-0.12.0/src/js/tsconfig.json +0 -13
  26. litestar_vite-0.12.0/src/js/vitest.config.ts +0 -8
  27. litestar_vite-0.12.0/src/js/vitest.workspace.ts +0 -4
  28. litestar_vite-0.12.0/src/py/litestar_vite/inertia/__init__.py +0 -34
  29. {litestar_vite-0.12.0 → litestar_vite-0.12.1}/.gitignore +0 -0
  30. {litestar_vite-0.12.0 → litestar_vite-0.12.1}/LICENSE +0 -0
  31. {litestar_vite-0.12.0 → litestar_vite-0.12.1}/README.md +0 -0
  32. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/__init__.py +0 -0
  33. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/__metadata__.py +0 -0
  34. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/cli.py +0 -0
  35. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/config.py +0 -0
  36. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/inertia/_utils.py +0 -0
  37. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/inertia/config.py +0 -0
  38. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/inertia/middleware.py +0 -0
  39. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/inertia/request.py +0 -0
  40. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/inertia/routes.py +0 -0
  41. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/inertia/types.py +0 -0
  42. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/loader.py +0 -0
  43. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/py.typed +0 -0
  44. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/templates/__init__.py +0 -0
  45. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/templates/index.html.j2 +0 -0
  46. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/templates/main.ts.j2 +0 -0
  47. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/templates/package.json.j2 +0 -0
  48. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/templates/styles.css.j2 +0 -0
  49. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/templates/tsconfig.json.j2 +0 -0
  50. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/litestar_vite/templates/vite.config.ts.j2 +0 -0
  51. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/__init__.py +0 -0
  52. /litestar_vite-0.12.0/src/py/tests/templates/__init__.py → /litestar_vite-0.12.1/tests/py.typed +0 -0
  53. {litestar_vite-0.12.0/src/py/tests/test_app → litestar_vite-0.12.1/tests/templates}/__init__.py +0 -0
  54. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/templates/index.html.j2 +0 -0
  55. {litestar_vite-0.12.0/src/py/tests/test_app/web → litestar_vite-0.12.1/tests/test_app}/__init__.py +0 -0
  56. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_app/app.py +0 -0
  57. {litestar_vite-0.12.0/src/py/tests/test_inertia → litestar_vite-0.12.1/tests/test_app/web}/__init__.py +0 -0
  58. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_app/web/public/.gitkeep +0 -0
  59. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_app/web/public/assets/main-l0sNRNKZ.js +0 -0
  60. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_app/web/public/assets/styles-l0sNRNKZ.js +0 -0
  61. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_app/web/public/manifest.json +0 -0
  62. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_app/web/resources/.gitkeep +0 -0
  63. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_app/web/resources/main.ts +0 -0
  64. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_app/web/resources/styles.css +0 -0
  65. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_app/web/templates/.gitkeep +0 -0
  66. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_app/web/templates/index.html +0 -0
  67. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_asset_loader.py +0 -0
  68. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_cli/__init__.py +0 -0
  69. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_cli/test_init.py +0 -0
  70. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_commands.py +0 -0
  71. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_config.py +0 -0
  72. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_inertia/templates/index.html.j2 +0 -0
  73. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_inertia/test_request.py +0 -0
  74. {litestar_vite-0.12.0/src/py → litestar_vite-0.12.1}/tests/test_inertia/test_routes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: litestar-vite
3
- Version: 0.12.0
3
+ Version: 0.12.1
4
4
  Summary: Vite plugin for Litestar
5
5
  Project-URL: Changelog, https://cofin.github.io/litestar-vite/latest/changelog
6
6
  Project-URL: Discord, https://discord.gg/X3FJqy8d2j
@@ -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.3",
17
- "litestar-vite-plugin": "^0.12.0",
18
- "@types/node": "^22.10.1",
16
+ "vite": "^6.0.6",
17
+ "litestar-vite-plugin": "^0.12.1",
18
+ "@types/node": "^22.10.2",
19
19
  }
20
- DEFAULT_DEPENDENCIES: dict[str, str] = {"axios": "^1.7.2"}
20
+ DEFAULT_DEPENDENCIES: dict[str, str] = {"axios": "^1.7.9"}
21
21
 
22
22
 
23
23
  def to_json(value: Any) -> str:
@@ -0,0 +1,37 @@
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 (
9
+ InertiaBack,
10
+ InertiaExternalRedirect,
11
+ InertiaRedirect,
12
+ InertiaResponse,
13
+ )
14
+
15
+ from .routes import generate_js_routes
16
+
17
+ __all__ = (
18
+ "InertiaBack",
19
+ "InertiaConfig",
20
+ "InertiaDetails",
21
+ "InertiaExternalRedirect",
22
+ "InertiaHeaders",
23
+ "InertiaMiddleware",
24
+ "InertiaPlugin",
25
+ "InertiaRedirect",
26
+ "InertiaRequest",
27
+ "InertiaResponse",
28
+ "create_inertia_exception_response",
29
+ "error",
30
+ "exception_to_http_response",
31
+ "generate_js_routes",
32
+ "get_shared_props",
33
+ "helpers",
34
+ "js_routes_script",
35
+ "lazy",
36
+ "share",
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.response import InertiaBack, InertiaRedirect, InertiaResponse, error
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
+ )
@@ -1,11 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
3
+ from contextlib import asynccontextmanager
4
+ from typing import TYPE_CHECKING, AsyncGenerator
4
5
 
5
- from anyio.from_thread import BlockingPortalProvider
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__ = ("config", "portal")
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
- self.portal = BlockingPortalProvider()
38
+ @asynccontextmanager
39
+ async def lifespan(self, app: Litestar) -> AsyncGenerator[None, None]:
40
+ """Lifespan to ensure the event loop is available."""
37
41
 
38
- def get_portal(self) -> BlockingPortalProvider:
39
- return self.portal
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 DeferredProp, InertiaBack, InertiaResponse, StaticProp
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