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.
- litestar_vite/__init__.py +54 -4
- litestar_vite/__metadata__.py +12 -7
- litestar_vite/_codegen/__init__.py +26 -0
- litestar_vite/_codegen/inertia.py +407 -0
- litestar_vite/_codegen/openapi.py +233 -0
- litestar_vite/_codegen/routes.py +653 -0
- litestar_vite/_codegen/ts.py +235 -0
- litestar_vite/_handler/__init__.py +8 -0
- litestar_vite/_handler/app.py +524 -0
- litestar_vite/_handler/routing.py +130 -0
- litestar_vite/cli.py +1147 -10
- litestar_vite/codegen.py +39 -0
- litestar_vite/commands.py +79 -0
- litestar_vite/config.py +1594 -70
- litestar_vite/deploy.py +355 -0
- litestar_vite/doctor.py +1179 -0
- litestar_vite/exceptions.py +78 -0
- litestar_vite/executor.py +316 -0
- litestar_vite/handler.py +9 -0
- litestar_vite/html_transform.py +426 -0
- litestar_vite/inertia/__init__.py +53 -0
- litestar_vite/inertia/_utils.py +114 -0
- litestar_vite/inertia/exception_handler.py +172 -0
- litestar_vite/inertia/helpers.py +1043 -0
- litestar_vite/inertia/middleware.py +54 -0
- litestar_vite/inertia/plugin.py +133 -0
- litestar_vite/inertia/request.py +286 -0
- litestar_vite/inertia/response.py +706 -0
- litestar_vite/inertia/types.py +316 -0
- litestar_vite/loader.py +462 -121
- litestar_vite/plugin.py +2160 -21
- litestar_vite/py.typed +0 -0
- litestar_vite/scaffolding/__init__.py +20 -0
- litestar_vite/scaffolding/generator.py +270 -0
- litestar_vite/scaffolding/templates.py +437 -0
- litestar_vite/templates/__init__.py +0 -0
- litestar_vite/templates/addons/tailwindcss/tailwind.css.j2 +1 -0
- litestar_vite/templates/angular/index.html.j2 +12 -0
- litestar_vite/templates/angular/openapi-ts.config.ts.j2 +18 -0
- litestar_vite/templates/angular/package.json.j2 +35 -0
- litestar_vite/templates/angular/src/app/app.component.css.j2 +3 -0
- litestar_vite/templates/angular/src/app/app.component.html.j2 +1 -0
- litestar_vite/templates/angular/src/app/app.component.ts.j2 +9 -0
- litestar_vite/templates/angular/src/app/app.config.ts.j2 +5 -0
- litestar_vite/templates/angular/src/main.ts.j2 +9 -0
- litestar_vite/templates/angular/src/styles.css.j2 +9 -0
- litestar_vite/templates/angular/tsconfig.app.json.j2 +34 -0
- litestar_vite/templates/angular/tsconfig.json.j2 +20 -0
- litestar_vite/templates/angular/vite.config.ts.j2 +21 -0
- litestar_vite/templates/angular-cli/.postcssrc.json.j2 +5 -0
- litestar_vite/templates/angular-cli/angular.json.j2 +36 -0
- litestar_vite/templates/angular-cli/openapi-ts.config.ts.j2 +18 -0
- litestar_vite/templates/angular-cli/package.json.j2 +27 -0
- litestar_vite/templates/angular-cli/proxy.conf.json.j2 +18 -0
- litestar_vite/templates/angular-cli/src/app/app.component.css.j2 +3 -0
- litestar_vite/templates/angular-cli/src/app/app.component.html.j2 +1 -0
- litestar_vite/templates/angular-cli/src/app/app.component.ts.j2 +9 -0
- litestar_vite/templates/angular-cli/src/app/app.config.ts.j2 +5 -0
- litestar_vite/templates/angular-cli/src/index.html.j2 +13 -0
- litestar_vite/templates/angular-cli/src/main.ts.j2 +6 -0
- litestar_vite/templates/angular-cli/src/styles.css.j2 +10 -0
- litestar_vite/templates/angular-cli/tailwind.config.js.j2 +4 -0
- litestar_vite/templates/angular-cli/tsconfig.app.json.j2 +16 -0
- litestar_vite/templates/angular-cli/tsconfig.json.j2 +26 -0
- litestar_vite/templates/angular-cli/tsconfig.spec.json.j2 +9 -0
- litestar_vite/templates/astro/astro.config.mjs.j2 +28 -0
- litestar_vite/templates/astro/openapi-ts.config.ts.j2 +15 -0
- litestar_vite/templates/astro/src/layouts/Layout.astro.j2 +63 -0
- litestar_vite/templates/astro/src/pages/index.astro.j2 +36 -0
- litestar_vite/templates/astro/src/styles/global.css.j2 +1 -0
- litestar_vite/templates/base/.gitignore.j2 +42 -0
- litestar_vite/templates/base/openapi-ts.config.ts.j2 +15 -0
- litestar_vite/templates/base/package.json.j2 +38 -0
- litestar_vite/templates/base/resources/vite-env.d.ts.j2 +1 -0
- litestar_vite/templates/base/tsconfig.json.j2 +37 -0
- litestar_vite/templates/htmx/src/main.js.j2 +8 -0
- litestar_vite/templates/htmx/templates/base.html.j2.j2 +56 -0
- litestar_vite/templates/htmx/templates/index.html.j2.j2 +13 -0
- litestar_vite/templates/htmx/vite.config.ts.j2 +40 -0
- litestar_vite/templates/nuxt/app.vue.j2 +29 -0
- litestar_vite/templates/nuxt/composables/useApi.ts.j2 +33 -0
- litestar_vite/templates/nuxt/nuxt.config.ts.j2 +31 -0
- litestar_vite/templates/nuxt/openapi-ts.config.ts.j2 +15 -0
- litestar_vite/templates/nuxt/pages/index.vue.j2 +54 -0
- litestar_vite/templates/react/index.html.j2 +13 -0
- litestar_vite/templates/react/src/App.css.j2 +56 -0
- litestar_vite/templates/react/src/App.tsx.j2 +19 -0
- litestar_vite/templates/react/src/main.tsx.j2 +10 -0
- litestar_vite/templates/react/vite.config.ts.j2 +39 -0
- litestar_vite/templates/react-inertia/index.html.j2 +14 -0
- litestar_vite/templates/react-inertia/package.json.j2 +46 -0
- litestar_vite/templates/react-inertia/resources/App.css.j2 +68 -0
- litestar_vite/templates/react-inertia/resources/main.tsx.j2 +17 -0
- litestar_vite/templates/react-inertia/resources/pages/Home.tsx.j2 +18 -0
- litestar_vite/templates/react-inertia/resources/ssr.tsx.j2 +19 -0
- litestar_vite/templates/react-inertia/vite.config.ts.j2 +59 -0
- litestar_vite/templates/react-router/index.html.j2 +12 -0
- litestar_vite/templates/react-router/src/App.css.j2 +17 -0
- litestar_vite/templates/react-router/src/App.tsx.j2 +7 -0
- litestar_vite/templates/react-router/src/main.tsx.j2 +10 -0
- litestar_vite/templates/react-router/vite.config.ts.j2 +39 -0
- litestar_vite/templates/react-tanstack/index.html.j2 +12 -0
- litestar_vite/templates/react-tanstack/openapi-ts.config.ts.j2 +18 -0
- litestar_vite/templates/react-tanstack/src/App.css.j2 +17 -0
- litestar_vite/templates/react-tanstack/src/main.tsx.j2 +21 -0
- litestar_vite/templates/react-tanstack/src/routeTree.gen.ts.j2 +7 -0
- litestar_vite/templates/react-tanstack/src/routes/__root.tsx.j2 +9 -0
- litestar_vite/templates/react-tanstack/src/routes/books.tsx.j2 +9 -0
- litestar_vite/templates/react-tanstack/src/routes/index.tsx.j2 +9 -0
- litestar_vite/templates/react-tanstack/vite.config.ts.j2 +39 -0
- litestar_vite/templates/svelte/index.html.j2 +13 -0
- litestar_vite/templates/svelte/src/App.svelte.j2 +30 -0
- litestar_vite/templates/svelte/src/app.css.j2 +45 -0
- litestar_vite/templates/svelte/src/main.ts.j2 +8 -0
- litestar_vite/templates/svelte/src/vite-env.d.ts.j2 +2 -0
- litestar_vite/templates/svelte/svelte.config.js.j2 +5 -0
- litestar_vite/templates/svelte/vite.config.ts.j2 +39 -0
- litestar_vite/templates/svelte-inertia/index.html.j2 +14 -0
- litestar_vite/templates/svelte-inertia/resources/app.css.j2 +21 -0
- litestar_vite/templates/svelte-inertia/resources/main.ts.j2 +11 -0
- litestar_vite/templates/svelte-inertia/resources/pages/Home.svelte.j2 +43 -0
- litestar_vite/templates/svelte-inertia/resources/vite-env.d.ts.j2 +2 -0
- litestar_vite/templates/svelte-inertia/svelte.config.js.j2 +5 -0
- litestar_vite/templates/svelte-inertia/vite.config.ts.j2 +37 -0
- litestar_vite/templates/sveltekit/openapi-ts.config.ts.j2 +15 -0
- litestar_vite/templates/sveltekit/src/app.css.j2 +40 -0
- litestar_vite/templates/sveltekit/src/app.html.j2 +12 -0
- litestar_vite/templates/sveltekit/src/hooks.server.ts.j2 +55 -0
- litestar_vite/templates/sveltekit/src/routes/+layout.svelte.j2 +12 -0
- litestar_vite/templates/sveltekit/src/routes/+page.svelte.j2 +34 -0
- litestar_vite/templates/sveltekit/svelte.config.js.j2 +12 -0
- litestar_vite/templates/sveltekit/tsconfig.json.j2 +14 -0
- litestar_vite/templates/sveltekit/vite.config.ts.j2 +31 -0
- litestar_vite/templates/vue/env.d.ts.j2 +7 -0
- litestar_vite/templates/vue/index.html.j2 +13 -0
- litestar_vite/templates/vue/src/App.vue.j2 +28 -0
- litestar_vite/templates/vue/src/main.ts.j2 +5 -0
- litestar_vite/templates/vue/src/style.css.j2 +45 -0
- litestar_vite/templates/vue/vite.config.ts.j2 +39 -0
- litestar_vite/templates/vue-inertia/env.d.ts.j2 +7 -0
- litestar_vite/templates/vue-inertia/index.html.j2 +14 -0
- litestar_vite/templates/vue-inertia/package.json.j2 +49 -0
- litestar_vite/templates/vue-inertia/resources/main.ts.j2 +18 -0
- litestar_vite/templates/vue-inertia/resources/pages/Home.vue.j2 +22 -0
- litestar_vite/templates/vue-inertia/resources/ssr.ts.j2 +21 -0
- litestar_vite/templates/vue-inertia/resources/style.css.j2 +21 -0
- litestar_vite/templates/vue-inertia/vite.config.ts.j2 +59 -0
- litestar_vite-0.15.0rc2.dist-info/METADATA +230 -0
- litestar_vite-0.15.0rc2.dist-info/RECORD +151 -0
- {litestar_vite-0.1.1.dist-info → litestar_vite-0.15.0rc2.dist-info}/WHEEL +1 -1
- litestar_vite/template_engine.py +0 -103
- litestar_vite-0.1.1.dist-info/METADATA +0 -68
- litestar_vite-0.1.1.dist-info/RECORD +0 -11
- {litestar_vite-0.1.1.dist-info → litestar_vite-0.15.0rc2.dist-info}/licenses/LICENSE +0 -0
litestar_vite/__init__.py
CHANGED
|
@@ -1,8 +1,58 @@
|
|
|
1
|
-
|
|
1
|
+
"""Litestar-Vite: Seamless integration between Litestar and Vite.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This package provides integration between the Litestar web framework and
|
|
4
|
+
Vite, the next-generation frontend build tool.
|
|
5
|
+
|
|
6
|
+
Basic usage:
|
|
7
|
+
from litestar import Litestar
|
|
8
|
+
from litestar_vite import VitePlugin, ViteConfig
|
|
9
|
+
|
|
10
|
+
app = Litestar(
|
|
11
|
+
plugins=[VitePlugin(config=ViteConfig(dev_mode=True))],
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
For more advanced configuration:
|
|
15
|
+
from litestar_vite import VitePlugin, ViteConfig, PathConfig, RuntimeConfig
|
|
16
|
+
|
|
17
|
+
app = Litestar(
|
|
18
|
+
plugins=[
|
|
19
|
+
VitePlugin(
|
|
20
|
+
config=ViteConfig(
|
|
21
|
+
mode="spa",
|
|
22
|
+
dev_mode=True,
|
|
23
|
+
paths=PathConfig(bundle_dir=Path("dist")),
|
|
24
|
+
runtime=RuntimeConfig(executor="bun"),
|
|
25
|
+
types=True,
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
],
|
|
29
|
+
)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from litestar_vite import inertia
|
|
33
|
+
from litestar_vite.config import (
|
|
34
|
+
DeployConfig,
|
|
35
|
+
ExternalDevServer,
|
|
36
|
+
InertiaConfig,
|
|
37
|
+
InertiaSSRConfig,
|
|
38
|
+
PathConfig,
|
|
39
|
+
RuntimeConfig,
|
|
40
|
+
TypeGenConfig,
|
|
41
|
+
ViteConfig,
|
|
42
|
+
)
|
|
4
43
|
from litestar_vite.loader import ViteAssetLoader
|
|
5
44
|
from litestar_vite.plugin import VitePlugin
|
|
6
|
-
from litestar_vite.template_engine import ViteTemplateEngine
|
|
7
45
|
|
|
8
|
-
__all__ = (
|
|
46
|
+
__all__ = (
|
|
47
|
+
"DeployConfig",
|
|
48
|
+
"ExternalDevServer",
|
|
49
|
+
"InertiaConfig",
|
|
50
|
+
"InertiaSSRConfig",
|
|
51
|
+
"PathConfig",
|
|
52
|
+
"RuntimeConfig",
|
|
53
|
+
"TypeGenConfig",
|
|
54
|
+
"ViteAssetLoader",
|
|
55
|
+
"ViteConfig",
|
|
56
|
+
"VitePlugin",
|
|
57
|
+
"inertia",
|
|
58
|
+
)
|
litestar_vite/__metadata__.py
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
"""Metadata for the Project."""
|
|
2
|
-
from __future__ import annotations
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, metadata, version
|
|
5
4
|
|
|
6
|
-
__all__ =
|
|
5
|
+
__all__ = ("__project__", "__version__")
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"""Name
|
|
7
|
+
try:
|
|
8
|
+
__version__ = version("litestar_vite")
|
|
9
|
+
"""Version of the project."""
|
|
10
|
+
__project__ = metadata("litestar_vite")["Name"]
|
|
11
|
+
"""Name of the project."""
|
|
12
|
+
except PackageNotFoundError: # pragma: no cover
|
|
13
|
+
__version__ = "0.0.0"
|
|
14
|
+
__project__ = "Litestar Vite"
|
|
15
|
+
finally:
|
|
16
|
+
del version, PackageNotFoundError, metadata
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Internal code generation implementation.
|
|
2
|
+
|
|
3
|
+
This package contains the implementation behind the public functions exposed via
|
|
4
|
+
``litestar_vite.codegen``.
|
|
5
|
+
|
|
6
|
+
We keep Litestar private OpenAPI integration and TypeScript conversion details
|
|
7
|
+
isolated here to make the public module easier to navigate and maintain.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from litestar_vite._codegen.inertia import InertiaPageMetadata, extract_inertia_pages, generate_inertia_pages_json
|
|
11
|
+
from litestar_vite._codegen.routes import (
|
|
12
|
+
RouteMetadata,
|
|
13
|
+
extract_route_metadata,
|
|
14
|
+
generate_routes_json,
|
|
15
|
+
generate_routes_ts,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = (
|
|
19
|
+
"InertiaPageMetadata",
|
|
20
|
+
"RouteMetadata",
|
|
21
|
+
"extract_inertia_pages",
|
|
22
|
+
"extract_route_metadata",
|
|
23
|
+
"generate_inertia_pages_json",
|
|
24
|
+
"generate_routes_json",
|
|
25
|
+
"generate_routes_ts",
|
|
26
|
+
)
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"""Inertia page-props metadata extraction and export."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
from contextlib import suppress
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
8
|
+
|
|
9
|
+
from litestar._openapi.datastructures import _get_normalized_schema_key # pyright: ignore[reportPrivateUsage]
|
|
10
|
+
from litestar.handlers import HTTPRouteHandler
|
|
11
|
+
from litestar.openapi.spec import Reference, Schema
|
|
12
|
+
from litestar.response.base import ASGIResponse
|
|
13
|
+
from litestar.routes import HTTPRoute
|
|
14
|
+
from litestar.types.builtin_types import NoneType
|
|
15
|
+
from litestar.typing import FieldDefinition
|
|
16
|
+
|
|
17
|
+
from litestar_vite._codegen.openapi import (
|
|
18
|
+
OpenAPISupport,
|
|
19
|
+
build_schema_name_map,
|
|
20
|
+
merge_generated_components_into_openapi,
|
|
21
|
+
openapi_components_schemas,
|
|
22
|
+
resolve_page_props_field_definition,
|
|
23
|
+
schema_name_from_ref,
|
|
24
|
+
)
|
|
25
|
+
from litestar_vite._codegen.ts import collect_ref_names, normalize_path, python_type_to_typescript, ts_type_from_openapi
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from litestar import Litestar
|
|
29
|
+
|
|
30
|
+
from litestar_vite.config import InertiaConfig, TypeGenConfig
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _str_list_factory() -> list[str]:
|
|
34
|
+
"""Return an empty ``list[str]`` (typed for pyright).
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
An empty list.
|
|
38
|
+
"""
|
|
39
|
+
return []
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class InertiaPageMetadata:
|
|
44
|
+
"""Metadata for a single Inertia page component."""
|
|
45
|
+
|
|
46
|
+
component: str
|
|
47
|
+
route_path: str
|
|
48
|
+
props_type: str | None = None
|
|
49
|
+
schema_ref: str | None = None
|
|
50
|
+
handler_name: str | None = None
|
|
51
|
+
ts_type: str | None = None
|
|
52
|
+
custom_types: list[str] = field(default_factory=_str_list_factory)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _get_return_type_name(handler: HTTPRouteHandler) -> "str | None":
|
|
56
|
+
field_definition = handler.parsed_fn_signature.return_type
|
|
57
|
+
excluded_types: tuple[type[Any], ...] = (NoneType, ASGIResponse)
|
|
58
|
+
if field_definition.is_subclass_of(excluded_types):
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
fn = handler.fn
|
|
62
|
+
with suppress(AttributeError):
|
|
63
|
+
return_annotation = fn.__annotations__.get("return")
|
|
64
|
+
if isinstance(return_annotation, str) and return_annotation:
|
|
65
|
+
return return_annotation
|
|
66
|
+
|
|
67
|
+
raw = field_definition.raw
|
|
68
|
+
if isinstance(raw, str):
|
|
69
|
+
return raw
|
|
70
|
+
if isinstance(raw, type):
|
|
71
|
+
return raw.__name__
|
|
72
|
+
origin: Any = None
|
|
73
|
+
with suppress(AttributeError):
|
|
74
|
+
origin = field_definition.origin
|
|
75
|
+
if isinstance(origin, type):
|
|
76
|
+
return origin.__name__
|
|
77
|
+
return str(raw)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _get_openapi_schema_ref(
|
|
81
|
+
handler: HTTPRouteHandler, openapi_schema: dict[str, Any] | None, route_path: str, method: str = "GET"
|
|
82
|
+
) -> "str | None":
|
|
83
|
+
if not openapi_schema:
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
paths = openapi_schema.get("paths", {})
|
|
87
|
+
path_item = paths.get(route_path, {})
|
|
88
|
+
operation = path_item.get(method.lower(), {})
|
|
89
|
+
|
|
90
|
+
responses = operation.get("responses", {})
|
|
91
|
+
success_response = responses.get("200", responses.get("2XX", {}))
|
|
92
|
+
content = success_response.get("content", {})
|
|
93
|
+
|
|
94
|
+
json_content = content.get("application/json", {})
|
|
95
|
+
schema = json_content.get("schema", {})
|
|
96
|
+
|
|
97
|
+
ref = schema.get("$ref")
|
|
98
|
+
return cast("str | None", ref) if ref else None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _extract_inertia_component(handler: HTTPRouteHandler) -> str | None:
|
|
102
|
+
opt = handler.opt or {}
|
|
103
|
+
component = opt.get("component") or opt.get("page")
|
|
104
|
+
return component if isinstance(component, str) and component else None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _infer_inertia_props_type(
|
|
108
|
+
component: str,
|
|
109
|
+
handler: HTTPRouteHandler,
|
|
110
|
+
schema_creator: Any,
|
|
111
|
+
page_schema_keys: dict[str, tuple[str, ...]],
|
|
112
|
+
page_schema_dicts: dict[str, dict[str, Any]],
|
|
113
|
+
*,
|
|
114
|
+
fallback_type: str,
|
|
115
|
+
) -> str | None:
|
|
116
|
+
if schema_creator is not None:
|
|
117
|
+
field_def, schema_result = resolve_page_props_field_definition(handler, schema_creator)
|
|
118
|
+
if field_def is not None and isinstance(schema_result, Reference):
|
|
119
|
+
page_schema_keys[component] = _get_normalized_schema_key(field_def)
|
|
120
|
+
return None
|
|
121
|
+
if isinstance(schema_result, Schema):
|
|
122
|
+
schema_dict = schema_result.to_schema()
|
|
123
|
+
page_schema_dicts[component] = schema_dict
|
|
124
|
+
return ts_type_from_openapi(schema_dict)
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
raw_type = _get_return_type_name(handler)
|
|
128
|
+
if not raw_type:
|
|
129
|
+
return None
|
|
130
|
+
props_type, _ = python_type_to_typescript(raw_type, fallback=fallback_type)
|
|
131
|
+
return props_type
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _finalize_inertia_pages(
|
|
135
|
+
pages: list[InertiaPageMetadata],
|
|
136
|
+
*,
|
|
137
|
+
openapi_support: OpenAPISupport,
|
|
138
|
+
page_schema_keys: dict[str, tuple[str, ...]],
|
|
139
|
+
page_schema_dicts: dict[str, dict[str, Any]],
|
|
140
|
+
) -> None:
|
|
141
|
+
context = openapi_support.context
|
|
142
|
+
if context is None:
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
generated_components = context.schema_registry.generate_components_schemas()
|
|
146
|
+
name_map = build_schema_name_map(context.schema_registry)
|
|
147
|
+
openapi_components = openapi_components_schemas(openapi_support.openapi_schema)
|
|
148
|
+
|
|
149
|
+
if openapi_support.openapi_schema is not None:
|
|
150
|
+
merge_generated_components_into_openapi(openapi_support.openapi_schema, generated_components)
|
|
151
|
+
|
|
152
|
+
for page in pages:
|
|
153
|
+
schema_key = page_schema_keys.get(page.component)
|
|
154
|
+
|
|
155
|
+
schema_name: str | None = None
|
|
156
|
+
if page.schema_ref:
|
|
157
|
+
schema_name = schema_name_from_ref(page.schema_ref)
|
|
158
|
+
elif schema_key:
|
|
159
|
+
schema_name = name_map.get(schema_key)
|
|
160
|
+
|
|
161
|
+
if schema_name:
|
|
162
|
+
page.ts_type = schema_name
|
|
163
|
+
page.props_type = schema_name
|
|
164
|
+
|
|
165
|
+
custom_types: set[str] = set()
|
|
166
|
+
if page.ts_type:
|
|
167
|
+
custom_types.add(page.ts_type)
|
|
168
|
+
|
|
169
|
+
if page.schema_ref:
|
|
170
|
+
openapi_schema_dict = openapi_components.get(page.ts_type or "")
|
|
171
|
+
if isinstance(openapi_schema_dict, dict):
|
|
172
|
+
custom_types.update(collect_ref_names(openapi_schema_dict))
|
|
173
|
+
else:
|
|
174
|
+
page_schema_dict = page_schema_dicts.get(page.component)
|
|
175
|
+
if isinstance(page_schema_dict, dict):
|
|
176
|
+
custom_types.update(collect_ref_names(page_schema_dict))
|
|
177
|
+
elif schema_key:
|
|
178
|
+
registered = context.schema_registry._schema_key_map.get( # pyright: ignore[reportPrivateUsage]
|
|
179
|
+
schema_key
|
|
180
|
+
)
|
|
181
|
+
if registered:
|
|
182
|
+
custom_types.update(collect_ref_names(registered.schema.to_schema()))
|
|
183
|
+
|
|
184
|
+
page.custom_types = sorted(custom_types)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def extract_inertia_pages(
|
|
188
|
+
app: "Litestar", *, openapi_schema: dict[str, Any] | None = None, fallback_type: "str" = "unknown"
|
|
189
|
+
) -> list[InertiaPageMetadata]:
|
|
190
|
+
pages: list[InertiaPageMetadata] = []
|
|
191
|
+
|
|
192
|
+
openapi_support = OpenAPISupport.from_app(app, openapi_schema)
|
|
193
|
+
|
|
194
|
+
page_schema_keys: dict[str, tuple[str, ...]] = {}
|
|
195
|
+
page_schema_dicts: dict[str, dict[str, Any]] = {}
|
|
196
|
+
|
|
197
|
+
for http_route, route_handler in _iter_route_handlers(app):
|
|
198
|
+
component = _extract_inertia_component(route_handler)
|
|
199
|
+
if not component:
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
normalized_path = normalize_path(str(http_route.path))
|
|
203
|
+
handler_name = route_handler.handler_name or route_handler.name
|
|
204
|
+
|
|
205
|
+
props_type = _infer_inertia_props_type(
|
|
206
|
+
component,
|
|
207
|
+
route_handler,
|
|
208
|
+
openapi_support.schema_creator,
|
|
209
|
+
page_schema_keys,
|
|
210
|
+
page_schema_dicts,
|
|
211
|
+
fallback_type=fallback_type,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
method = next(iter(route_handler.http_methods), "GET") if route_handler.http_methods else "GET"
|
|
215
|
+
schema_ref = _get_openapi_schema_ref(route_handler, openapi_schema, normalized_path, method=str(method))
|
|
216
|
+
|
|
217
|
+
pages.append(
|
|
218
|
+
InertiaPageMetadata(
|
|
219
|
+
component=component,
|
|
220
|
+
route_path=normalized_path,
|
|
221
|
+
props_type=props_type,
|
|
222
|
+
schema_ref=schema_ref,
|
|
223
|
+
handler_name=handler_name,
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
if openapi_support.enabled:
|
|
228
|
+
_finalize_inertia_pages(
|
|
229
|
+
pages,
|
|
230
|
+
openapi_support=openapi_support,
|
|
231
|
+
page_schema_keys=page_schema_keys,
|
|
232
|
+
page_schema_dicts=page_schema_dicts,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
return pages
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _iter_route_handlers(app: "Litestar") -> "list[tuple[HTTPRoute, HTTPRouteHandler]]":
|
|
239
|
+
"""Iterate over HTTP route handlers in an app.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
A list of (http_route, route_handler) tuples.
|
|
243
|
+
"""
|
|
244
|
+
handlers: list[tuple[HTTPRoute, HTTPRouteHandler]] = []
|
|
245
|
+
for route in app.routes:
|
|
246
|
+
if isinstance(route, HTTPRoute):
|
|
247
|
+
handlers.extend((route, route_handler) for route_handler in route.route_handlers)
|
|
248
|
+
return handlers
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _fallback_ts_type(types_config: "TypeGenConfig | None") -> str:
|
|
252
|
+
fallback_type = types_config.fallback_type if types_config is not None else "unknown"
|
|
253
|
+
return "any" if fallback_type == "any" else "unknown"
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _ts_type_from_value(value: Any, *, fallback_ts_type: str) -> str:
|
|
257
|
+
ts_type = fallback_ts_type
|
|
258
|
+
if value is None:
|
|
259
|
+
ts_type = "null"
|
|
260
|
+
elif isinstance(value, bool):
|
|
261
|
+
ts_type = "boolean"
|
|
262
|
+
elif isinstance(value, str):
|
|
263
|
+
ts_type = "string"
|
|
264
|
+
elif isinstance(value, (int, float)):
|
|
265
|
+
ts_type = "number"
|
|
266
|
+
elif isinstance(value, (bytes, bytearray, Path)):
|
|
267
|
+
ts_type = "string"
|
|
268
|
+
elif isinstance(value, (list, tuple, set, frozenset)):
|
|
269
|
+
ts_type = f"{fallback_ts_type}[]"
|
|
270
|
+
elif isinstance(value, dict):
|
|
271
|
+
ts_type = f"Record<string, {fallback_ts_type}>"
|
|
272
|
+
return ts_type
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _should_register_value_schema(value: Any) -> bool:
|
|
276
|
+
if value is None:
|
|
277
|
+
return False
|
|
278
|
+
return not isinstance(value, (bool, str, int, float, bytes, bytearray, Path, list, tuple, set, frozenset, dict))
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _build_inertia_shared_props(
|
|
282
|
+
app: "Litestar",
|
|
283
|
+
*,
|
|
284
|
+
openapi_schema: dict[str, Any] | None,
|
|
285
|
+
include_default_auth: bool,
|
|
286
|
+
include_default_flash: bool,
|
|
287
|
+
inertia_config: "InertiaConfig | None",
|
|
288
|
+
types_config: "TypeGenConfig | None",
|
|
289
|
+
) -> dict[str, dict[str, Any]]:
|
|
290
|
+
"""Build shared props metadata (built-ins + configured props).
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Mapping of shared prop name to metadata payload.
|
|
294
|
+
"""
|
|
295
|
+
fallback_ts_type = _fallback_ts_type(types_config)
|
|
296
|
+
|
|
297
|
+
shared_props: dict[str, dict[str, Any]] = {
|
|
298
|
+
"errors": {"type": "Record<string, string[]>", "optional": True},
|
|
299
|
+
"csrf_token": {"type": "string", "optional": True},
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if include_default_auth or include_default_flash:
|
|
303
|
+
shared_props["auth"] = {"type": "AuthData", "optional": True}
|
|
304
|
+
shared_props["flash"] = {"type": "FlashMessages", "optional": True}
|
|
305
|
+
|
|
306
|
+
if inertia_config is None:
|
|
307
|
+
return shared_props
|
|
308
|
+
|
|
309
|
+
openapi_support = OpenAPISupport.from_app(app, openapi_schema)
|
|
310
|
+
shared_schema_keys: dict[str, tuple[str, ...]] = {}
|
|
311
|
+
|
|
312
|
+
for key, value in inertia_config.extra_static_page_props.items():
|
|
313
|
+
if not key:
|
|
314
|
+
continue
|
|
315
|
+
|
|
316
|
+
shared_props[key] = {"type": _ts_type_from_value(value, fallback_ts_type=fallback_ts_type), "optional": True}
|
|
317
|
+
|
|
318
|
+
if openapi_support.enabled and isinstance(openapi_schema, dict) and _should_register_value_schema(value):
|
|
319
|
+
try:
|
|
320
|
+
field_def = FieldDefinition.from_annotation(value.__class__)
|
|
321
|
+
schema_result = openapi_support.schema_creator.for_field_definition(field_def) # type: ignore[union-attr]
|
|
322
|
+
if isinstance(schema_result, Reference):
|
|
323
|
+
shared_schema_keys[key] = _get_normalized_schema_key(field_def)
|
|
324
|
+
except (AttributeError, TypeError, ValueError): # pragma: no cover - defensive
|
|
325
|
+
pass
|
|
326
|
+
|
|
327
|
+
for key in inertia_config.extra_session_page_props:
|
|
328
|
+
if not key:
|
|
329
|
+
continue
|
|
330
|
+
shared_props.setdefault(key, {"type": fallback_ts_type, "optional": True})
|
|
331
|
+
|
|
332
|
+
if not (
|
|
333
|
+
openapi_support.context
|
|
334
|
+
and openapi_support.schema_creator
|
|
335
|
+
and isinstance(openapi_schema, dict)
|
|
336
|
+
and shared_schema_keys
|
|
337
|
+
):
|
|
338
|
+
return shared_props
|
|
339
|
+
|
|
340
|
+
generated_components = openapi_support.context.schema_registry.generate_components_schemas()
|
|
341
|
+
name_map = build_schema_name_map(openapi_support.context.schema_registry)
|
|
342
|
+
merge_generated_components_into_openapi(openapi_schema, generated_components)
|
|
343
|
+
|
|
344
|
+
for prop_name, schema_key in shared_schema_keys.items():
|
|
345
|
+
type_name = name_map.get(schema_key)
|
|
346
|
+
if type_name:
|
|
347
|
+
shared_props[prop_name]["type"] = type_name
|
|
348
|
+
|
|
349
|
+
return shared_props
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def generate_inertia_pages_json(
|
|
353
|
+
app: "Litestar",
|
|
354
|
+
*,
|
|
355
|
+
openapi_schema: dict[str, Any] | None = None,
|
|
356
|
+
include_default_auth: bool = True,
|
|
357
|
+
include_default_flash: bool = True,
|
|
358
|
+
inertia_config: "InertiaConfig | None" = None,
|
|
359
|
+
types_config: "TypeGenConfig | None" = None,
|
|
360
|
+
) -> dict[str, Any]:
|
|
361
|
+
"""Generate Inertia pages metadata JSON.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
An Inertia pages metadata payload as a dictionary.
|
|
365
|
+
"""
|
|
366
|
+
pages_metadata = extract_inertia_pages(
|
|
367
|
+
app,
|
|
368
|
+
openapi_schema=openapi_schema,
|
|
369
|
+
fallback_type=types_config.fallback_type if types_config is not None else "unknown",
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
pages_dict: dict[str, dict[str, Any]] = {}
|
|
373
|
+
for page in pages_metadata:
|
|
374
|
+
page_data: dict[str, Any] = {"route": page.route_path}
|
|
375
|
+
if page.props_type:
|
|
376
|
+
page_data["propsType"] = page.props_type
|
|
377
|
+
if page.ts_type:
|
|
378
|
+
page_data["tsType"] = page.ts_type
|
|
379
|
+
if page.custom_types:
|
|
380
|
+
page_data["customTypes"] = page.custom_types
|
|
381
|
+
if page.schema_ref:
|
|
382
|
+
page_data["schemaRef"] = page.schema_ref
|
|
383
|
+
if page.handler_name:
|
|
384
|
+
page_data["handler"] = page.handler_name
|
|
385
|
+
pages_dict[page.component] = page_data
|
|
386
|
+
|
|
387
|
+
shared_props = _build_inertia_shared_props(
|
|
388
|
+
app,
|
|
389
|
+
openapi_schema=openapi_schema,
|
|
390
|
+
include_default_auth=include_default_auth,
|
|
391
|
+
include_default_flash=include_default_flash,
|
|
392
|
+
inertia_config=inertia_config,
|
|
393
|
+
types_config=types_config,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
root: dict[str, Any] = {
|
|
397
|
+
"pages": pages_dict,
|
|
398
|
+
"sharedProps": shared_props,
|
|
399
|
+
"typeGenConfig": {"includeDefaultAuth": include_default_auth, "includeDefaultFlash": include_default_flash},
|
|
400
|
+
"generatedAt": datetime.datetime.now(tz=datetime.timezone.utc).isoformat(),
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if types_config is not None:
|
|
404
|
+
root["typeImportPaths"] = types_config.type_import_paths
|
|
405
|
+
root["fallbackType"] = types_config.fallback_type
|
|
406
|
+
|
|
407
|
+
return root
|