litestar-vite 0.7.1__py3-none-any.whl → 0.8.1__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.
Potentially problematic release.
This version of litestar-vite might be problematic. Click here for more details.
- litestar_vite/__init__.py +9 -0
- litestar_vite/__metadata__.py +18 -0
- litestar_vite/cli.py +324 -0
- litestar_vite/commands.py +131 -0
- litestar_vite/config.py +173 -0
- litestar_vite/inertia/__init__.py +34 -0
- litestar_vite/inertia/_utils.py +63 -0
- litestar_vite/inertia/config.py +29 -0
- litestar_vite/inertia/exception_handler.py +116 -0
- litestar_vite/inertia/middleware.py +52 -0
- litestar_vite/inertia/plugin.py +64 -0
- litestar_vite/inertia/request.py +116 -0
- litestar_vite/inertia/response.py +373 -0
- litestar_vite/inertia/routes.py +54 -0
- litestar_vite/inertia/types.py +39 -0
- litestar_vite/loader.py +221 -0
- litestar_vite/plugin.py +136 -0
- litestar_vite/py.typed +0 -0
- litestar_vite/template_engine.py +103 -0
- litestar_vite/templates/__init__.py +0 -0
- litestar_vite/templates/index.html.j2 +16 -0
- litestar_vite/templates/main.ts.j2 +1 -0
- litestar_vite/templates/package.json.j2 +11 -0
- litestar_vite/templates/styles.css.j2 +0 -0
- litestar_vite/templates/tsconfig.json.j2 +30 -0
- litestar_vite/templates/vite.config.ts.j2 +38 -0
- {litestar_vite-0.7.1.dist-info → litestar_vite-0.8.1.dist-info}/METADATA +1 -1
- litestar_vite-0.8.1.dist-info/RECORD +30 -0
- litestar_vite-0.7.1.dist-info/RECORD +0 -4
- {litestar_vite-0.7.1.dist-info → litestar_vite-0.8.1.dist-info}/WHEEL +0 -0
- {litestar_vite-0.7.1.dist-info → litestar_vite-0.8.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from litestar_vite.inertia.types import InertiaHeaderType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InertiaHeaders(str, Enum):
|
|
11
|
+
"""Enum for Inertia Headers"""
|
|
12
|
+
|
|
13
|
+
ENABLED = "X-Inertia"
|
|
14
|
+
VERSION = "X-Inertia-Version"
|
|
15
|
+
PARTIAL_DATA = "X-Inertia-Partial-Data"
|
|
16
|
+
PARTIAL_COMPONENT = "X-Inertia-Partial-Component"
|
|
17
|
+
LOCATION = "X-Inertia-Location"
|
|
18
|
+
REFERER = "Referer"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_enabled_header(enabled: bool = True) -> dict[str, Any]:
|
|
22
|
+
"""True if inertia is enabled."""
|
|
23
|
+
|
|
24
|
+
return {InertiaHeaders.ENABLED.value: "true" if enabled else "false"}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_version_header(version: str) -> dict[str, Any]:
|
|
28
|
+
"""Return headers for change swap method response."""
|
|
29
|
+
return {InertiaHeaders.VERSION.value: version}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_partial_data_header(partial: str) -> dict[str, Any]:
|
|
33
|
+
"""Return headers for a partial data response."""
|
|
34
|
+
return {InertiaHeaders.PARTIAL_DATA.value: partial}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_partial_component_header(partial: str) -> dict[str, Any]:
|
|
38
|
+
"""Return headers for a partial data response."""
|
|
39
|
+
return {InertiaHeaders.PARTIAL_COMPONENT.value: partial}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_headers(inertia_headers: InertiaHeaderType) -> dict[str, Any]:
|
|
43
|
+
"""Return headers for Inertia responses."""
|
|
44
|
+
if not inertia_headers:
|
|
45
|
+
msg = "Value for inertia_headers cannot be None."
|
|
46
|
+
raise ValueError(msg)
|
|
47
|
+
inertia_headers_dict: dict[str, Callable[..., dict[str, Any]]] = {
|
|
48
|
+
"enabled": get_enabled_header,
|
|
49
|
+
"partial_data": get_partial_data_header,
|
|
50
|
+
"partial_component": get_partial_component_header,
|
|
51
|
+
"version": get_version_header,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
header: dict[str, Any] = {}
|
|
55
|
+
response: dict[str, Any]
|
|
56
|
+
key: str
|
|
57
|
+
value: Any
|
|
58
|
+
|
|
59
|
+
for key, value in inertia_headers.items():
|
|
60
|
+
if value is not None:
|
|
61
|
+
response = inertia_headers_dict[key](value)
|
|
62
|
+
header.update(response)
|
|
63
|
+
return header
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
__all__ = ("InertiaConfig",)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class InertiaConfig:
|
|
11
|
+
"""Configuration for InertiaJS support."""
|
|
12
|
+
|
|
13
|
+
root_template: str = "index.html"
|
|
14
|
+
"""Name of the root template to use.
|
|
15
|
+
|
|
16
|
+
This must be a path that is found by the Vite Plugin template config
|
|
17
|
+
"""
|
|
18
|
+
component_opt_key: str = "component"
|
|
19
|
+
"""An identifier to use on routes to get the inertia component to render."""
|
|
20
|
+
exclude_from_js_routes_key: str = "exclude_from_routes"
|
|
21
|
+
"""An identifier to use on routes to exclude a route from the generated routes typescript file."""
|
|
22
|
+
redirect_unauthorized_to: str | None = None
|
|
23
|
+
"""Optionally supply a path where unauthorized requests should redirect."""
|
|
24
|
+
redirect_404: str | None = None
|
|
25
|
+
"""Optionally supply a path where 404 requests should redirect."""
|
|
26
|
+
extra_static_page_props: dict[str, Any] = field(default_factory=dict)
|
|
27
|
+
"""A dictionary of values to automatically add in to page props on every response."""
|
|
28
|
+
extra_session_page_props: set[str] = field(default_factory=set)
|
|
29
|
+
"""A set of session keys for which the value automatically be added (if it exists) to the response."""
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
5
|
+
|
|
6
|
+
from litestar import MediaType
|
|
7
|
+
from litestar.connection import Request
|
|
8
|
+
from litestar.connection.base import AuthT, StateT, UserT
|
|
9
|
+
from litestar.exceptions import (
|
|
10
|
+
HTTPException,
|
|
11
|
+
ImproperlyConfiguredException,
|
|
12
|
+
InternalServerException,
|
|
13
|
+
NotAuthorizedException,
|
|
14
|
+
NotFoundException,
|
|
15
|
+
PermissionDeniedException,
|
|
16
|
+
)
|
|
17
|
+
from litestar.exceptions.responses import (
|
|
18
|
+
create_debug_response, # pyright: ignore[reportUnknownVariableType]
|
|
19
|
+
create_exception_response, # pyright: ignore[reportUnknownVariableType]
|
|
20
|
+
)
|
|
21
|
+
from litestar.plugins.flash import flash
|
|
22
|
+
from litestar.repository.exceptions import (
|
|
23
|
+
ConflictError, # pyright: ignore[reportUnknownVariableType,reportAttributeAccessIssue]
|
|
24
|
+
NotFoundError, # pyright: ignore[reportUnknownVariableType,reportAttributeAccessIssue]
|
|
25
|
+
RepositoryError, # pyright: ignore[reportUnknownVariableType,reportAttributeAccessIssue]
|
|
26
|
+
)
|
|
27
|
+
from litestar.response import Response
|
|
28
|
+
from litestar.status_codes import (
|
|
29
|
+
HTTP_400_BAD_REQUEST,
|
|
30
|
+
HTTP_401_UNAUTHORIZED,
|
|
31
|
+
HTTP_404_NOT_FOUND,
|
|
32
|
+
HTTP_405_METHOD_NOT_ALLOWED,
|
|
33
|
+
HTTP_409_CONFLICT,
|
|
34
|
+
HTTP_422_UNPROCESSABLE_ENTITY,
|
|
35
|
+
HTTP_500_INTERNAL_SERVER_ERROR,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
from litestar_vite.inertia.response import InertiaBack, InertiaRedirect, InertiaResponse, error
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from litestar.connection import Request
|
|
42
|
+
from litestar.connection.base import AuthT, StateT, UserT
|
|
43
|
+
from litestar.response import Response
|
|
44
|
+
|
|
45
|
+
from litestar_vite.inertia.plugin import InertiaPlugin
|
|
46
|
+
|
|
47
|
+
FIELD_ERR_RE = re.compile(r"field `(.+)`$")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class _HTTPConflictException(HTTPException):
|
|
51
|
+
"""Request conflict with the current state of the target resource."""
|
|
52
|
+
|
|
53
|
+
status_code: int = HTTP_409_CONFLICT
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def exception_to_http_response(request: Request[UserT, AuthT, StateT], exc: Exception) -> Response[Any]:
|
|
57
|
+
"""Handler for all exceptions subclassed from HTTPException."""
|
|
58
|
+
inertia_enabled = getattr(request, "inertia_enabled", False) or getattr(request, "is_inertia", False)
|
|
59
|
+
|
|
60
|
+
if not inertia_enabled:
|
|
61
|
+
if isinstance(exc, NotFoundError):
|
|
62
|
+
http_exc = NotFoundException
|
|
63
|
+
elif isinstance(exc, (RepositoryError, ConflictError)):
|
|
64
|
+
http_exc = _HTTPConflictException # type: ignore[assignment]
|
|
65
|
+
else:
|
|
66
|
+
http_exc = InternalServerException # type: ignore[assignment]
|
|
67
|
+
if request.app.debug and http_exc not in (PermissionDeniedException, NotFoundError):
|
|
68
|
+
return cast("Response[Any]", create_debug_response(request, exc))
|
|
69
|
+
return cast("Response[Any]", create_exception_response(request, http_exc(detail=str(exc.__cause__)))) # pyright: ignore[reportUnknownArgumentType]
|
|
70
|
+
return create_inertia_exception_response(request, exc)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def create_inertia_exception_response(request: Request[UserT, AuthT, StateT], exc: Exception) -> Response[Any]:
|
|
74
|
+
"""Create the inertia exception response"""
|
|
75
|
+
is_inertia = getattr(request, "is_inertia", False)
|
|
76
|
+
status_code = getattr(exc, "status_code", HTTP_500_INTERNAL_SERVER_ERROR)
|
|
77
|
+
preferred_type = MediaType.HTML if not is_inertia else MediaType.JSON
|
|
78
|
+
detail = getattr(exc, "detail", "") # litestar exceptions
|
|
79
|
+
extras = getattr(exc, "extra", "") # msgspec exceptions
|
|
80
|
+
content: dict[str, Any] = {"status_code": status_code, "message": getattr(exc, "detail", "")}
|
|
81
|
+
inertia_plugin = cast("InertiaPlugin", request.app.plugins.get("InertiaPlugin"))
|
|
82
|
+
if extras:
|
|
83
|
+
content.update({"extra": extras})
|
|
84
|
+
try:
|
|
85
|
+
flash(request, detail, category="error")
|
|
86
|
+
except (AttributeError, ImproperlyConfiguredException):
|
|
87
|
+
msg = "Unable to set `flash` session state. A valid session was not found for this request."
|
|
88
|
+
request.logger.warning(msg)
|
|
89
|
+
if extras and len(extras) >= 1:
|
|
90
|
+
message = extras[0]
|
|
91
|
+
default_field = f"root.{message.get('key')}" if message.get("key", None) is not None else "root" # type: ignore
|
|
92
|
+
error_detail = cast("str", message.get("message", detail)) # type: ignore[union-attr] # pyright: ignore[reportUnknownMemberType]
|
|
93
|
+
match = FIELD_ERR_RE.search(error_detail)
|
|
94
|
+
field = match.group(1) if match else default_field
|
|
95
|
+
if isinstance(message, dict):
|
|
96
|
+
error(request, field, error_detail if error_detail else detail)
|
|
97
|
+
if status_code in {HTTP_422_UNPROCESSABLE_ENTITY, HTTP_400_BAD_REQUEST}:
|
|
98
|
+
return InertiaBack(request)
|
|
99
|
+
if isinstance(exc, PermissionDeniedException):
|
|
100
|
+
return InertiaBack(request)
|
|
101
|
+
if (status_code == HTTP_401_UNAUTHORIZED or isinstance(exc, NotAuthorizedException)) and (
|
|
102
|
+
inertia_plugin.config.redirect_unauthorized_to is not None
|
|
103
|
+
and request.url.path != inertia_plugin.config.redirect_unauthorized_to
|
|
104
|
+
):
|
|
105
|
+
return InertiaRedirect(request, redirect_to=inertia_plugin.config.redirect_unauthorized_to)
|
|
106
|
+
|
|
107
|
+
if status_code in {HTTP_404_NOT_FOUND, HTTP_405_METHOD_NOT_ALLOWED} and (
|
|
108
|
+
inertia_plugin.config.redirect_404 is not None and request.url.path != inertia_plugin.config.redirect_404
|
|
109
|
+
):
|
|
110
|
+
return InertiaRedirect(request, redirect_to=inertia_plugin.config.redirect_404)
|
|
111
|
+
|
|
112
|
+
return InertiaResponse[Any](
|
|
113
|
+
media_type=preferred_type,
|
|
114
|
+
content=content,
|
|
115
|
+
status_code=status_code,
|
|
116
|
+
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from litestar import Request
|
|
6
|
+
from litestar.middleware import AbstractMiddleware
|
|
7
|
+
from litestar.types import Receive, Scope, Send
|
|
8
|
+
|
|
9
|
+
from litestar_vite.inertia.response import InertiaRedirect
|
|
10
|
+
from litestar_vite.plugin import VitePlugin
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from litestar.connection.base import (
|
|
14
|
+
AuthT,
|
|
15
|
+
StateT,
|
|
16
|
+
UserT,
|
|
17
|
+
)
|
|
18
|
+
from litestar.types import ASGIApp, Receive, Scope, Send
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def redirect_on_asset_version_mismatch(request: Request[UserT, AuthT, StateT]) -> InertiaRedirect | None:
|
|
22
|
+
if getattr(request, "is_inertia", None) is None:
|
|
23
|
+
return None
|
|
24
|
+
inertia_version = request.headers.get("X-Inertia-Version")
|
|
25
|
+
if inertia_version is None:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
vite_plugin = request.app.plugins.get(VitePlugin)
|
|
29
|
+
template_engine = vite_plugin.template_config.to_engine()
|
|
30
|
+
if inertia_version == template_engine.asset_loader.version_id:
|
|
31
|
+
return None
|
|
32
|
+
return InertiaRedirect(request, redirect_to=str(request.url))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class InertiaMiddleware(AbstractMiddleware):
|
|
36
|
+
def __init__(self, app: ASGIApp) -> None:
|
|
37
|
+
super().__init__(app)
|
|
38
|
+
self.app = app
|
|
39
|
+
|
|
40
|
+
async def __call__(
|
|
41
|
+
self,
|
|
42
|
+
scope: "Scope",
|
|
43
|
+
receive: "Receive",
|
|
44
|
+
send: "Send",
|
|
45
|
+
) -> None:
|
|
46
|
+
request = Request[Any, Any, Any](scope=scope)
|
|
47
|
+
redirect = await redirect_on_asset_version_mismatch(request)
|
|
48
|
+
if redirect is not None:
|
|
49
|
+
response = redirect.to_asgi_response(app=None, request=request) # pyright: ignore[reportUnknownMemberType]
|
|
50
|
+
await response(scope, receive, send)
|
|
51
|
+
else:
|
|
52
|
+
await self.app(scope, receive, send)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from litestar.exceptions import ImproperlyConfiguredException
|
|
6
|
+
from litestar.middleware import DefineMiddleware
|
|
7
|
+
from litestar.middleware.session import SessionMiddleware
|
|
8
|
+
from litestar.plugins import InitPluginProtocol
|
|
9
|
+
from litestar.security.session_auth.middleware import MiddlewareWrapper
|
|
10
|
+
from litestar.utils.predicates import is_class_and_subclass
|
|
11
|
+
|
|
12
|
+
from litestar_vite.inertia.exception_handler import exception_to_http_response
|
|
13
|
+
from litestar_vite.inertia.middleware import InertiaMiddleware
|
|
14
|
+
from litestar_vite.inertia.request import InertiaRequest
|
|
15
|
+
from litestar_vite.inertia.response import InertiaResponse
|
|
16
|
+
from litestar_vite.inertia.routes import generate_js_routes
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from litestar import Litestar
|
|
20
|
+
from litestar.config.app import AppConfig
|
|
21
|
+
|
|
22
|
+
from litestar_vite.inertia.config import InertiaConfig
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def set_js_routes(app: Litestar) -> None:
|
|
26
|
+
"""Generate the route structure of the application on startup."""
|
|
27
|
+
js_routes = generate_js_routes(app)
|
|
28
|
+
app.state.js_routes = js_routes
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class InertiaPlugin(InitPluginProtocol):
|
|
32
|
+
"""Inertia plugin."""
|
|
33
|
+
|
|
34
|
+
__slots__ = ("config",)
|
|
35
|
+
|
|
36
|
+
def __init__(self, config: InertiaConfig) -> None:
|
|
37
|
+
"""Initialize ``Inertia``.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
config: Inertia configuration.
|
|
41
|
+
"""
|
|
42
|
+
self.config = config
|
|
43
|
+
|
|
44
|
+
def on_app_init(self, app_config: AppConfig) -> AppConfig:
|
|
45
|
+
"""Configure application for use with Vite.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
app_config: The :class:`AppConfig <litestar.config.app.AppConfig>` instance.
|
|
49
|
+
"""
|
|
50
|
+
for mw in app_config.middleware:
|
|
51
|
+
if isinstance(mw, DefineMiddleware) and is_class_and_subclass(
|
|
52
|
+
mw.middleware,
|
|
53
|
+
(MiddlewareWrapper, SessionMiddleware),
|
|
54
|
+
):
|
|
55
|
+
break
|
|
56
|
+
else:
|
|
57
|
+
msg = "The Inertia plugin require a session middleware."
|
|
58
|
+
raise ImproperlyConfiguredException(msg)
|
|
59
|
+
app_config.exception_handlers.update({Exception: exception_to_http_response}) # pyright: ignore[reportUnknownMemberType]
|
|
60
|
+
app_config.request_class = InertiaRequest
|
|
61
|
+
app_config.response_class = InertiaResponse
|
|
62
|
+
app_config.middleware.append(InertiaMiddleware)
|
|
63
|
+
app_config.on_startup.append(set_js_routes)
|
|
64
|
+
return app_config
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from functools import cached_property
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
from urllib.parse import unquote
|
|
6
|
+
|
|
7
|
+
from litestar import Request
|
|
8
|
+
from litestar.connection.base import (
|
|
9
|
+
AuthT,
|
|
10
|
+
StateT,
|
|
11
|
+
UserT,
|
|
12
|
+
empty_receive,
|
|
13
|
+
empty_send,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from litestar_vite.inertia._utils import InertiaHeaders
|
|
17
|
+
|
|
18
|
+
__all__ = ("InertiaDetails", "InertiaRequest")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from litestar.types import Receive, Scope, Send
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class InertiaDetails:
|
|
26
|
+
"""InertiaDetails holds all the values sent by Inertia client in headers and provide convenient properties."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, request: Request[UserT, AuthT, StateT]) -> None:
|
|
29
|
+
"""Initialize :class:`InertiaDetails`"""
|
|
30
|
+
self.request = request
|
|
31
|
+
|
|
32
|
+
def _get_header_value(self, name: InertiaHeaders) -> str | None:
|
|
33
|
+
"""Parse request header
|
|
34
|
+
|
|
35
|
+
Check for uri encoded header and unquotes it in readable format.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
if value := self.request.headers.get(name.value.lower()):
|
|
39
|
+
is_uri_encoded = self.request.headers.get(f"{name.value.lower()}-uri-autoencoded") == "true"
|
|
40
|
+
return unquote(value) if is_uri_encoded else value
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
def _get_route_component(self) -> str | None:
|
|
44
|
+
"""Get the route component.
|
|
45
|
+
|
|
46
|
+
Checks for the `component` key within the route configuration.
|
|
47
|
+
"""
|
|
48
|
+
rh = self.request.scope.get("route_handler") # pyright: ignore[reportUnknownMemberType]
|
|
49
|
+
if rh:
|
|
50
|
+
return rh.opt.get("component")
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def __bool__(self) -> bool:
|
|
54
|
+
"""Check if request is sent by an Inertia client."""
|
|
55
|
+
return self._get_header_value(InertiaHeaders.ENABLED) == "true"
|
|
56
|
+
|
|
57
|
+
@cached_property
|
|
58
|
+
def route_component(self) -> str | None:
|
|
59
|
+
"""Partial Data Reload."""
|
|
60
|
+
return self._get_route_component()
|
|
61
|
+
|
|
62
|
+
@cached_property
|
|
63
|
+
def partial_component(self) -> str | None:
|
|
64
|
+
"""Partial Data Reload."""
|
|
65
|
+
return self._get_header_value(InertiaHeaders.PARTIAL_COMPONENT)
|
|
66
|
+
|
|
67
|
+
@cached_property
|
|
68
|
+
def partial_data(self) -> str | None:
|
|
69
|
+
"""Partial Data Reload."""
|
|
70
|
+
return self._get_header_value(InertiaHeaders.PARTIAL_DATA)
|
|
71
|
+
|
|
72
|
+
@cached_property
|
|
73
|
+
def referer(self) -> str | None:
|
|
74
|
+
"""Partial Data Reload."""
|
|
75
|
+
return self._get_header_value(InertiaHeaders.REFERER)
|
|
76
|
+
|
|
77
|
+
@cached_property
|
|
78
|
+
def is_partial_render(self) -> bool:
|
|
79
|
+
"""Is Partial Data Reload."""
|
|
80
|
+
return bool(self.partial_component == self.route_component and self.partial_data)
|
|
81
|
+
|
|
82
|
+
@cached_property
|
|
83
|
+
def partial_keys(self) -> list[str]:
|
|
84
|
+
"""Is Partial Data Reload."""
|
|
85
|
+
return self.partial_data.split(",") if self.partial_data is not None else []
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class InertiaRequest(Request[UserT, AuthT, StateT]):
|
|
89
|
+
"""Inertia Request class to work with Inertia client."""
|
|
90
|
+
|
|
91
|
+
__slots__ = ("inertia",)
|
|
92
|
+
|
|
93
|
+
def __init__(self, scope: Scope, receive: Receive = empty_receive, send: Send = empty_send) -> None:
|
|
94
|
+
"""Initialize :class:`InertiaRequest`"""
|
|
95
|
+
super().__init__(scope=scope, receive=receive, send=send)
|
|
96
|
+
self.inertia = InertiaDetails(self)
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def is_inertia(self) -> bool:
|
|
100
|
+
"""True if the request contained inertia headers."""
|
|
101
|
+
return bool(self.inertia)
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def inertia_enabled(self) -> bool:
|
|
105
|
+
"""True if the route handler contains an inertia enabled configuration."""
|
|
106
|
+
return bool(self.inertia.route_component is not None)
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def is_partial_render(self) -> bool:
|
|
110
|
+
"""True if the route handler contains an inertia enabled configuration."""
|
|
111
|
+
return self.inertia.is_partial_render
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def partial_keys(self) -> set[str]:
|
|
115
|
+
"""True if the route handler contains an inertia enabled configuration."""
|
|
116
|
+
return set(self.inertia.partial_keys)
|