litestar-vite 0.1.1__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 (154) hide show
  1. litestar_vite/__init__.py +54 -4
  2. litestar_vite/__metadata__.py +12 -7
  3. litestar_vite/_codegen/__init__.py +26 -0
  4. litestar_vite/_codegen/inertia.py +407 -0
  5. litestar_vite/_codegen/openapi.py +233 -0
  6. litestar_vite/_codegen/routes.py +653 -0
  7. litestar_vite/_codegen/ts.py +235 -0
  8. litestar_vite/_handler/__init__.py +8 -0
  9. litestar_vite/_handler/app.py +524 -0
  10. litestar_vite/_handler/routing.py +130 -0
  11. litestar_vite/cli.py +1147 -10
  12. litestar_vite/codegen.py +39 -0
  13. litestar_vite/commands.py +79 -0
  14. litestar_vite/config.py +1594 -70
  15. litestar_vite/deploy.py +355 -0
  16. litestar_vite/doctor.py +1179 -0
  17. litestar_vite/exceptions.py +78 -0
  18. litestar_vite/executor.py +316 -0
  19. litestar_vite/handler.py +9 -0
  20. litestar_vite/html_transform.py +426 -0
  21. litestar_vite/inertia/__init__.py +53 -0
  22. litestar_vite/inertia/_utils.py +114 -0
  23. litestar_vite/inertia/exception_handler.py +172 -0
  24. litestar_vite/inertia/helpers.py +1043 -0
  25. litestar_vite/inertia/middleware.py +54 -0
  26. litestar_vite/inertia/plugin.py +133 -0
  27. litestar_vite/inertia/request.py +286 -0
  28. litestar_vite/inertia/response.py +706 -0
  29. litestar_vite/inertia/types.py +316 -0
  30. litestar_vite/loader.py +462 -121
  31. litestar_vite/plugin.py +2160 -21
  32. litestar_vite/py.typed +0 -0
  33. litestar_vite/scaffolding/__init__.py +20 -0
  34. litestar_vite/scaffolding/generator.py +270 -0
  35. litestar_vite/scaffolding/templates.py +437 -0
  36. litestar_vite/templates/__init__.py +0 -0
  37. litestar_vite/templates/addons/tailwindcss/tailwind.css.j2 +1 -0
  38. litestar_vite/templates/angular/index.html.j2 +12 -0
  39. litestar_vite/templates/angular/openapi-ts.config.ts.j2 +18 -0
  40. litestar_vite/templates/angular/package.json.j2 +35 -0
  41. litestar_vite/templates/angular/src/app/app.component.css.j2 +3 -0
  42. litestar_vite/templates/angular/src/app/app.component.html.j2 +1 -0
  43. litestar_vite/templates/angular/src/app/app.component.ts.j2 +9 -0
  44. litestar_vite/templates/angular/src/app/app.config.ts.j2 +5 -0
  45. litestar_vite/templates/angular/src/main.ts.j2 +9 -0
  46. litestar_vite/templates/angular/src/styles.css.j2 +9 -0
  47. litestar_vite/templates/angular/tsconfig.app.json.j2 +34 -0
  48. litestar_vite/templates/angular/tsconfig.json.j2 +20 -0
  49. litestar_vite/templates/angular/vite.config.ts.j2 +21 -0
  50. litestar_vite/templates/angular-cli/.postcssrc.json.j2 +5 -0
  51. litestar_vite/templates/angular-cli/angular.json.j2 +36 -0
  52. litestar_vite/templates/angular-cli/openapi-ts.config.ts.j2 +18 -0
  53. litestar_vite/templates/angular-cli/package.json.j2 +27 -0
  54. litestar_vite/templates/angular-cli/proxy.conf.json.j2 +18 -0
  55. litestar_vite/templates/angular-cli/src/app/app.component.css.j2 +3 -0
  56. litestar_vite/templates/angular-cli/src/app/app.component.html.j2 +1 -0
  57. litestar_vite/templates/angular-cli/src/app/app.component.ts.j2 +9 -0
  58. litestar_vite/templates/angular-cli/src/app/app.config.ts.j2 +5 -0
  59. litestar_vite/templates/angular-cli/src/index.html.j2 +13 -0
  60. litestar_vite/templates/angular-cli/src/main.ts.j2 +6 -0
  61. litestar_vite/templates/angular-cli/src/styles.css.j2 +10 -0
  62. litestar_vite/templates/angular-cli/tailwind.config.js.j2 +4 -0
  63. litestar_vite/templates/angular-cli/tsconfig.app.json.j2 +16 -0
  64. litestar_vite/templates/angular-cli/tsconfig.json.j2 +26 -0
  65. litestar_vite/templates/angular-cli/tsconfig.spec.json.j2 +9 -0
  66. litestar_vite/templates/astro/astro.config.mjs.j2 +28 -0
  67. litestar_vite/templates/astro/openapi-ts.config.ts.j2 +15 -0
  68. litestar_vite/templates/astro/src/layouts/Layout.astro.j2 +63 -0
  69. litestar_vite/templates/astro/src/pages/index.astro.j2 +36 -0
  70. litestar_vite/templates/astro/src/styles/global.css.j2 +1 -0
  71. litestar_vite/templates/base/.gitignore.j2 +42 -0
  72. litestar_vite/templates/base/openapi-ts.config.ts.j2 +15 -0
  73. litestar_vite/templates/base/package.json.j2 +38 -0
  74. litestar_vite/templates/base/resources/vite-env.d.ts.j2 +1 -0
  75. litestar_vite/templates/base/tsconfig.json.j2 +37 -0
  76. litestar_vite/templates/htmx/src/main.js.j2 +8 -0
  77. litestar_vite/templates/htmx/templates/base.html.j2.j2 +56 -0
  78. litestar_vite/templates/htmx/templates/index.html.j2.j2 +13 -0
  79. litestar_vite/templates/htmx/vite.config.ts.j2 +40 -0
  80. litestar_vite/templates/nuxt/app.vue.j2 +29 -0
  81. litestar_vite/templates/nuxt/composables/useApi.ts.j2 +33 -0
  82. litestar_vite/templates/nuxt/nuxt.config.ts.j2 +31 -0
  83. litestar_vite/templates/nuxt/openapi-ts.config.ts.j2 +15 -0
  84. litestar_vite/templates/nuxt/pages/index.vue.j2 +54 -0
  85. litestar_vite/templates/react/index.html.j2 +13 -0
  86. litestar_vite/templates/react/src/App.css.j2 +56 -0
  87. litestar_vite/templates/react/src/App.tsx.j2 +19 -0
  88. litestar_vite/templates/react/src/main.tsx.j2 +10 -0
  89. litestar_vite/templates/react/vite.config.ts.j2 +39 -0
  90. litestar_vite/templates/react-inertia/index.html.j2 +14 -0
  91. litestar_vite/templates/react-inertia/package.json.j2 +46 -0
  92. litestar_vite/templates/react-inertia/resources/App.css.j2 +68 -0
  93. litestar_vite/templates/react-inertia/resources/main.tsx.j2 +17 -0
  94. litestar_vite/templates/react-inertia/resources/pages/Home.tsx.j2 +18 -0
  95. litestar_vite/templates/react-inertia/resources/ssr.tsx.j2 +19 -0
  96. litestar_vite/templates/react-inertia/vite.config.ts.j2 +59 -0
  97. litestar_vite/templates/react-router/index.html.j2 +12 -0
  98. litestar_vite/templates/react-router/src/App.css.j2 +17 -0
  99. litestar_vite/templates/react-router/src/App.tsx.j2 +7 -0
  100. litestar_vite/templates/react-router/src/main.tsx.j2 +10 -0
  101. litestar_vite/templates/react-router/vite.config.ts.j2 +39 -0
  102. litestar_vite/templates/react-tanstack/index.html.j2 +12 -0
  103. litestar_vite/templates/react-tanstack/openapi-ts.config.ts.j2 +18 -0
  104. litestar_vite/templates/react-tanstack/src/App.css.j2 +17 -0
  105. litestar_vite/templates/react-tanstack/src/main.tsx.j2 +21 -0
  106. litestar_vite/templates/react-tanstack/src/routeTree.gen.ts.j2 +7 -0
  107. litestar_vite/templates/react-tanstack/src/routes/__root.tsx.j2 +9 -0
  108. litestar_vite/templates/react-tanstack/src/routes/books.tsx.j2 +9 -0
  109. litestar_vite/templates/react-tanstack/src/routes/index.tsx.j2 +9 -0
  110. litestar_vite/templates/react-tanstack/vite.config.ts.j2 +39 -0
  111. litestar_vite/templates/svelte/index.html.j2 +13 -0
  112. litestar_vite/templates/svelte/src/App.svelte.j2 +30 -0
  113. litestar_vite/templates/svelte/src/app.css.j2 +45 -0
  114. litestar_vite/templates/svelte/src/main.ts.j2 +8 -0
  115. litestar_vite/templates/svelte/src/vite-env.d.ts.j2 +2 -0
  116. litestar_vite/templates/svelte/svelte.config.js.j2 +5 -0
  117. litestar_vite/templates/svelte/vite.config.ts.j2 +39 -0
  118. litestar_vite/templates/svelte-inertia/index.html.j2 +14 -0
  119. litestar_vite/templates/svelte-inertia/resources/app.css.j2 +21 -0
  120. litestar_vite/templates/svelte-inertia/resources/main.ts.j2 +11 -0
  121. litestar_vite/templates/svelte-inertia/resources/pages/Home.svelte.j2 +43 -0
  122. litestar_vite/templates/svelte-inertia/resources/vite-env.d.ts.j2 +2 -0
  123. litestar_vite/templates/svelte-inertia/svelte.config.js.j2 +5 -0
  124. litestar_vite/templates/svelte-inertia/vite.config.ts.j2 +37 -0
  125. litestar_vite/templates/sveltekit/openapi-ts.config.ts.j2 +15 -0
  126. litestar_vite/templates/sveltekit/src/app.css.j2 +40 -0
  127. litestar_vite/templates/sveltekit/src/app.html.j2 +12 -0
  128. litestar_vite/templates/sveltekit/src/hooks.server.ts.j2 +55 -0
  129. litestar_vite/templates/sveltekit/src/routes/+layout.svelte.j2 +12 -0
  130. litestar_vite/templates/sveltekit/src/routes/+page.svelte.j2 +34 -0
  131. litestar_vite/templates/sveltekit/svelte.config.js.j2 +12 -0
  132. litestar_vite/templates/sveltekit/tsconfig.json.j2 +14 -0
  133. litestar_vite/templates/sveltekit/vite.config.ts.j2 +31 -0
  134. litestar_vite/templates/vue/env.d.ts.j2 +7 -0
  135. litestar_vite/templates/vue/index.html.j2 +13 -0
  136. litestar_vite/templates/vue/src/App.vue.j2 +28 -0
  137. litestar_vite/templates/vue/src/main.ts.j2 +5 -0
  138. litestar_vite/templates/vue/src/style.css.j2 +45 -0
  139. litestar_vite/templates/vue/vite.config.ts.j2 +39 -0
  140. litestar_vite/templates/vue-inertia/env.d.ts.j2 +7 -0
  141. litestar_vite/templates/vue-inertia/index.html.j2 +14 -0
  142. litestar_vite/templates/vue-inertia/package.json.j2 +49 -0
  143. litestar_vite/templates/vue-inertia/resources/main.ts.j2 +18 -0
  144. litestar_vite/templates/vue-inertia/resources/pages/Home.vue.j2 +22 -0
  145. litestar_vite/templates/vue-inertia/resources/ssr.ts.j2 +21 -0
  146. litestar_vite/templates/vue-inertia/resources/style.css.j2 +21 -0
  147. litestar_vite/templates/vue-inertia/vite.config.ts.j2 +59 -0
  148. litestar_vite-0.15.0rc2.dist-info/METADATA +230 -0
  149. litestar_vite-0.15.0rc2.dist-info/RECORD +151 -0
  150. {litestar_vite-0.1.1.dist-info → litestar_vite-0.15.0rc2.dist-info}/WHEEL +1 -1
  151. litestar_vite/template_engine.py +0 -103
  152. litestar_vite-0.1.1.dist-info/METADATA +0 -68
  153. litestar_vite-0.1.1.dist-info/RECORD +0 -11
  154. {litestar_vite-0.1.1.dist-info → litestar_vite-0.15.0rc2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,316 @@
1
+ """Inertia protocol types and serialization helpers.
2
+
3
+ This module defines the Python-side data structures for the Inertia.js protocol and provides
4
+ helpers to serialize dataclass instances into the camelCase shape expected by the client.
5
+ """
6
+
7
+ import re
8
+ from dataclasses import dataclass, field, fields, is_dataclass
9
+ from typing import Any, Generic, Literal, TypedDict, TypeVar, cast
10
+
11
+ __all__ = (
12
+ "DeferredPropsConfig",
13
+ "InertiaHeaderType",
14
+ "MergeStrategy",
15
+ "PageProps",
16
+ "ScrollPagination",
17
+ "ScrollPropsConfig",
18
+ "to_camel_case",
19
+ "to_inertia_dict",
20
+ )
21
+
22
+
23
+ T = TypeVar("T")
24
+
25
+ MergeStrategy = Literal["append", "prepend", "deep"]
26
+
27
+ _SNAKE_CASE_PATTERN = re.compile(r"_([a-z])")
28
+
29
+
30
+ def to_camel_case(snake_str: str) -> str:
31
+ """Convert snake_case string to camelCase.
32
+
33
+ Args:
34
+ snake_str: A snake_case string.
35
+
36
+ Returns:
37
+ The camelCase equivalent.
38
+
39
+ Examples:
40
+ >>> to_camel_case("encrypt_history")
41
+ 'encryptHistory'
42
+ >>> to_camel_case("deep_merge_props")
43
+ 'deepMergeProps'
44
+ """
45
+ return _SNAKE_CASE_PATTERN.sub(lambda m: m.group(1).upper(), snake_str)
46
+
47
+
48
+ def _is_dataclass_instance(value: Any) -> bool:
49
+ return is_dataclass(value) and not isinstance(value, type)
50
+
51
+
52
+ def _convert_value(value: Any) -> Any:
53
+ """Recursively convert a value for Inertia.js protocol.
54
+
55
+ Handles nested dataclasses, dicts, and lists without using asdict()
56
+ to avoid Python 3.10/3.11 bugs with dict[str, list[str]] types.
57
+
58
+ Returns:
59
+ The converted value.
60
+ """
61
+ if _is_dataclass_instance(value):
62
+ return to_inertia_dict(value)
63
+ if isinstance(value, dict):
64
+ return {k: _convert_value(v) for k, v in value.items()} # pyright: ignore[reportUnknownVariableType]
65
+ if isinstance(value, (list, tuple)):
66
+ return type(value)(_convert_value(v) for v in value) # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
67
+ return value
68
+
69
+
70
+ def to_inertia_dict(obj: Any, required_fields: "set[str] | None" = None) -> dict[str, Any]:
71
+ """Convert a dataclass to a dict with camelCase keys for Inertia.js protocol.
72
+
73
+ Args:
74
+ obj: A dataclass instance.
75
+ required_fields: Set of field names that should always be included (even if None).
76
+
77
+ Returns:
78
+ A dictionary with camelCase keys, excluding None values for optional fields.
79
+
80
+ Note:
81
+ This function avoids using dataclasses.asdict() directly because of a bug
82
+ in Python 3.10/3.11 that fails when processing dict[str, list[str]] types.
83
+ See: https://github.com/python/cpython/issues/103000
84
+ """
85
+ if not _is_dataclass_instance(obj):
86
+ return cast("dict[str, Any]", obj)
87
+
88
+ required_fields = required_fields or set()
89
+ result: dict[str, Any] = {}
90
+
91
+ for dc_field in fields(obj):
92
+ field_name = dc_field.name
93
+ value = getattr(obj, field_name)
94
+ if value is None and field_name not in required_fields:
95
+ continue
96
+
97
+ value = _convert_value(value)
98
+ camel_key = to_camel_case(field_name)
99
+ result[camel_key] = value
100
+
101
+ return result
102
+
103
+
104
+ def _str_list_factory() -> list[str]:
105
+ """Factory function for empty string list (typed for pyright).
106
+
107
+ Returns:
108
+ An empty list.
109
+ """
110
+ return []
111
+
112
+
113
+ @dataclass
114
+ class DeferredPropsConfig:
115
+ """Configuration for deferred props (v2 feature).
116
+
117
+ Deferred props are loaded lazily after the initial page render.
118
+ This allows for faster initial page loads by deferring non-critical data.
119
+ """
120
+
121
+ group: str = "default"
122
+ props: list[str] = field(default_factory=_str_list_factory)
123
+
124
+
125
+ @dataclass
126
+ class ScrollPropsConfig:
127
+ """Configuration for infinite scroll (v2 feature)."""
128
+
129
+ page_name: str = "page"
130
+ previous_page: "int | None" = None
131
+ next_page: "int | None" = None
132
+ current_page: int = 1
133
+
134
+
135
+ @dataclass
136
+ class ScrollPagination(Generic[T]):
137
+ """Pagination container optimized for infinite scroll.
138
+
139
+ A generic pagination type that works seamlessly with Inertia's infinite
140
+ scroll feature. Can be constructed directly or created from any pagination
141
+ container using ``create_from()``.
142
+
143
+ Attributes:
144
+ items: The paginated items for the current page.
145
+ total: Total number of items across all pages.
146
+ limit: Maximum items per page (page size).
147
+ offset: Number of items skipped from the start.
148
+
149
+ Example::
150
+
151
+ from litestar_vite.inertia.types import ScrollPagination
152
+
153
+ Direct construction::
154
+
155
+ @get("/users", component="Users", infinite_scroll=True)
156
+ async def list_users() -> ScrollPagination[User]:
157
+ users = await fetch_users(limit=10, offset=0)
158
+ return ScrollPagination(items=users, total=100, limit=10, offset=0)
159
+
160
+ From an existing pagination container::
161
+
162
+ @get("/posts", component="Posts", infinite_scroll=True)
163
+ async def list_posts() -> ScrollPagination[Post]:
164
+ pagination = await repo.list_paginated(limit=10, offset=0)
165
+ return ScrollPagination.create_from(pagination)
166
+ """
167
+
168
+ items: list[T]
169
+ total: int
170
+ limit: int
171
+ offset: int
172
+
173
+ @classmethod
174
+ def create_from(cls, pagination: Any) -> "ScrollPagination[T]":
175
+ """Create from any pagination container (auto-detects type).
176
+
177
+ Supports OffsetPagination, ClassicPagination, and any custom pagination
178
+ class with standard pagination attributes.
179
+
180
+ Args:
181
+ pagination: Any pagination container with ``items`` attribute.
182
+
183
+ Returns:
184
+ A ScrollPagination instance with normalized offset-based metadata.
185
+
186
+ Example::
187
+
188
+ from litestar.pagination import OffsetPagination, ClassicPagination
189
+
190
+ From OffsetPagination::
191
+
192
+ offset_page = OffsetPagination(items=[...], limit=10, offset=20, total=100)
193
+ scroll = ScrollPagination.create_from(offset_page)
194
+
195
+ From ClassicPagination::
196
+
197
+ classic_page = ClassicPagination(items=[...], page_size=10, current_page=3, total_pages=10)
198
+ scroll = ScrollPagination.create_from(classic_page)
199
+ """
200
+ items = pagination.items
201
+ if meta := _extract_offset_pagination(pagination, items):
202
+ total, limit, offset = meta
203
+ return cls(items=items, total=total, limit=limit, offset=offset)
204
+ if meta := _extract_classic_pagination(pagination, items):
205
+ total, limit, offset = meta
206
+ return cls(items=items, total=total, limit=limit, offset=offset)
207
+ return cls(items=items, total=len(items), limit=len(items), offset=0)
208
+
209
+
210
+ def _extract_offset_pagination(pagination: Any, items: list[Any]) -> tuple[int, int, int] | None:
211
+ try:
212
+ limit = pagination.limit
213
+ offset = pagination.offset
214
+ except AttributeError:
215
+ return None
216
+
217
+ try:
218
+ total = pagination.total
219
+ except AttributeError:
220
+ total = len(items)
221
+
222
+ if not (isinstance(limit, int) and isinstance(offset, int) and isinstance(total, int)):
223
+ return None
224
+
225
+ return total, limit, offset
226
+
227
+
228
+ def _extract_classic_pagination(pagination: Any, items: list[Any]) -> tuple[int, int, int] | None:
229
+ try:
230
+ page_size = pagination.page_size
231
+ current_page = pagination.current_page
232
+ except AttributeError:
233
+ return None
234
+
235
+ try:
236
+ total_pages = pagination.total_pages
237
+ except AttributeError:
238
+ total_pages = 1
239
+
240
+ if not (isinstance(page_size, int) and isinstance(current_page, int) and isinstance(total_pages, int)):
241
+ return None
242
+
243
+ offset = (current_page - 1) * page_size
244
+ total = total_pages * page_size
245
+ return total, page_size, offset
246
+
247
+
248
+ @dataclass
249
+ class PageProps(Generic[T]):
250
+ """Inertia Page Props Type.
251
+
252
+ This represents the page object sent to the Inertia client.
253
+ See: https://inertiajs.com/the-protocol
254
+
255
+ Note: Field names use snake_case in Python but are serialized to camelCase
256
+ for the Inertia.js protocol using `to_inertia_dict()`.
257
+
258
+ Attributes:
259
+ component: JavaScript component name to render.
260
+ url: Current page URL.
261
+ version: Asset version identifier for cache busting.
262
+ props: Page data/props passed to the component.
263
+ encrypt_history: Whether to encrypt browser history state (v2).
264
+ clear_history: Whether to clear encrypted history state (v2).
265
+ merge_props: Props to append during navigation (v2).
266
+ prepend_props: Props to prepend during navigation (v2).
267
+ deep_merge_props: Props to deep merge during navigation (v2).
268
+ match_props_on: Keys for matching items during merge (v2).
269
+ deferred_props: Configuration for lazy-loaded props (v2).
270
+ scroll_props: Configuration for infinite scroll (v2).
271
+ """
272
+
273
+ component: str
274
+ url: str
275
+ version: str
276
+ props: dict[str, Any]
277
+
278
+ encrypt_history: bool = False
279
+ clear_history: bool = False
280
+
281
+ merge_props: "list[str] | None" = None
282
+ prepend_props: "list[str] | None" = None
283
+ deep_merge_props: "list[str] | None" = None
284
+ match_props_on: "dict[str, list[str]] | None" = None
285
+
286
+ deferred_props: "dict[str, list[str]] | None" = None
287
+
288
+ scroll_props: "ScrollPropsConfig | None" = None
289
+
290
+ def to_dict(self) -> dict[str, Any]:
291
+ """Convert to Inertia.js protocol format with camelCase keys.
292
+
293
+ Returns:
294
+ The Inertia protocol dictionary.
295
+ """
296
+ return to_inertia_dict(self, required_fields={"component", "url", "version", "props"})
297
+
298
+
299
+ @dataclass
300
+ class InertiaProps(Generic[T]):
301
+ """Inertia Props Type."""
302
+
303
+ page: PageProps[T]
304
+
305
+
306
+ class InertiaHeaderType(TypedDict, total=False):
307
+ """Type for inertia_headers parameter in get_headers()."""
308
+
309
+ enabled: "bool | None"
310
+ version: "str | None"
311
+ location: "str | None"
312
+ partial_data: "str | None"
313
+ partial_component: "str | None"
314
+ partial_except: "str | None"
315
+ reset: "str | None"
316
+ error_bag: "str | None"