litestar-vite 0.15.0__py3-none-any.whl → 0.15.0rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. litestar_vite/_codegen/__init__.py +26 -0
  2. litestar_vite/_codegen/inertia.py +407 -0
  3. litestar_vite/{codegen/_openapi.py → _codegen/openapi.py} +11 -58
  4. litestar_vite/{codegen/_routes.py → _codegen/routes.py} +43 -110
  5. litestar_vite/{codegen/_ts.py → _codegen/ts.py} +19 -19
  6. litestar_vite/_handler/__init__.py +8 -0
  7. litestar_vite/{handler/_app.py → _handler/app.py} +29 -117
  8. litestar_vite/cli.py +254 -155
  9. litestar_vite/codegen.py +39 -0
  10. litestar_vite/commands.py +6 -0
  11. litestar_vite/{config/__init__.py → config.py} +726 -99
  12. litestar_vite/deploy.py +3 -14
  13. litestar_vite/doctor.py +6 -8
  14. litestar_vite/executor.py +1 -45
  15. litestar_vite/handler.py +9 -0
  16. litestar_vite/html_transform.py +5 -148
  17. litestar_vite/inertia/__init__.py +0 -24
  18. litestar_vite/inertia/_utils.py +0 -5
  19. litestar_vite/inertia/exception_handler.py +16 -22
  20. litestar_vite/inertia/helpers.py +18 -546
  21. litestar_vite/inertia/plugin.py +11 -77
  22. litestar_vite/inertia/request.py +0 -48
  23. litestar_vite/inertia/response.py +17 -113
  24. litestar_vite/inertia/types.py +0 -19
  25. litestar_vite/loader.py +7 -7
  26. litestar_vite/plugin.py +2184 -0
  27. litestar_vite/templates/angular/package.json.j2 +1 -2
  28. litestar_vite/templates/angular-cli/package.json.j2 +1 -2
  29. litestar_vite/templates/base/package.json.j2 +1 -2
  30. litestar_vite/templates/react-inertia/package.json.j2 +1 -2
  31. litestar_vite/templates/vue-inertia/package.json.j2 +1 -2
  32. {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/METADATA +5 -5
  33. {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/RECORD +36 -49
  34. litestar_vite/codegen/__init__.py +0 -48
  35. litestar_vite/codegen/_export.py +0 -229
  36. litestar_vite/codegen/_inertia.py +0 -619
  37. litestar_vite/codegen/_utils.py +0 -141
  38. litestar_vite/config/_constants.py +0 -97
  39. litestar_vite/config/_deploy.py +0 -70
  40. litestar_vite/config/_inertia.py +0 -241
  41. litestar_vite/config/_paths.py +0 -63
  42. litestar_vite/config/_runtime.py +0 -235
  43. litestar_vite/config/_spa.py +0 -93
  44. litestar_vite/config/_types.py +0 -94
  45. litestar_vite/handler/__init__.py +0 -9
  46. litestar_vite/inertia/precognition.py +0 -274
  47. litestar_vite/plugin/__init__.py +0 -687
  48. litestar_vite/plugin/_process.py +0 -185
  49. litestar_vite/plugin/_proxy.py +0 -689
  50. litestar_vite/plugin/_proxy_headers.py +0 -244
  51. litestar_vite/plugin/_static.py +0 -37
  52. litestar_vite/plugin/_utils.py +0 -489
  53. /litestar_vite/{handler/_routing.py → _handler/routing.py} +0 -0
  54. {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/WHEEL +0 -0
  55. {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/licenses/LICENSE +0 -0
@@ -1,274 +0,0 @@
1
- """Precognition support for real-time form validation.
2
-
3
- Precognition is a Laravel protocol for running server-side validation without
4
- executing handler side effects. This module provides Litestar integration.
5
-
6
- Usage:
7
- 1. Enable in config: ``InertiaConfig(precognition=True)``
8
- 2. Add ``@precognition`` decorator to route handlers
9
-
10
- The plugin automatically handles validation failures (422 responses).
11
- The decorator prevents handler execution on successful validation (204 responses).
12
-
13
- See: https://laravel.com/docs/precognition
14
-
15
- Note on Rate Limiting:
16
- Real-time validation can result in many requests. Laravel has no official
17
- rate limiting solution for Precognition. Consider:
18
- - Throttling Precognition requests separately from normal requests
19
- - Using debounce on the frontend (laravel-precognition libraries do this)
20
- - Implementing custom rate limiting that checks for Precognition header
21
- """
22
-
23
- from functools import wraps
24
- from typing import TYPE_CHECKING, Any
25
-
26
- from litestar import MediaType, Response
27
- from litestar.exceptions import ValidationException
28
- from litestar.status_codes import HTTP_204_NO_CONTENT, HTTP_422_UNPROCESSABLE_ENTITY
29
-
30
- from litestar_vite.inertia._utils import InertiaHeaders
31
-
32
- if TYPE_CHECKING:
33
- from collections.abc import Callable
34
-
35
- from litestar import Request
36
-
37
- __all__ = (
38
- "PrecognitionResponse",
39
- "create_precognition_exception_handler",
40
- "normalize_validation_errors",
41
- "precognition",
42
- )
43
-
44
-
45
- def normalize_validation_errors(exc: ValidationException, validate_only: "set[str] | None" = None) -> "dict[str, Any]":
46
- """Normalize Litestar validation errors to Laravel format.
47
-
48
- Laravel's Precognition protocol expects errors in this format:
49
- ```json
50
- {
51
- "message": "The given data was invalid.",
52
- "errors": {
53
- "email": ["The email field is required."],
54
- "password": ["The password must be at least 8 characters."]
55
- }
56
- }
57
- ```
58
-
59
- Args:
60
- exc: The ValidationException from Litestar.
61
- validate_only: If provided, only include errors for these fields.
62
- Used for partial field validation as the user types.
63
-
64
- Returns:
65
- A dict in Laravel's validation error format.
66
- """
67
- errors: "dict[str, list[str]]" = {}
68
-
69
- # Litestar's ValidationException.detail can be:
70
- # - A string message
71
- # - A list of error dicts with 'key', 'message', and 'source'
72
- # - Other structured data
73
- detail = exc.detail
74
-
75
- if isinstance(detail, list):
76
- for error in detail: # pyright: ignore[reportUnknownVariableType]
77
- if isinstance(error, dict):
78
- key: str = str(error.get("key", "")) # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType]
79
- message: str = str(error.get("message", str(error))) # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType]
80
- source: str = str(error.get("source", "")) # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType]
81
-
82
- # Build field name from source and key
83
- if source and key:
84
- field_name = f"{source}.{key}" if source != "body" else key
85
- elif key:
86
- field_name = key
87
- else:
88
- field_name = "_root"
89
-
90
- # Filter by validate_only if specified
91
- if validate_only and field_name not in validate_only:
92
- continue
93
-
94
- if field_name not in errors:
95
- errors[field_name] = []
96
- errors[field_name].append(message)
97
- elif isinstance(detail, str): # pyright: ignore[reportUnnecessaryIsInstance]
98
- errors["_root"] = [detail]
99
-
100
- return {"message": "The given data was invalid.", "errors": errors}
101
-
102
-
103
- class PrecognitionResponse(Response[Any]):
104
- """Response for successful Precognition validation.
105
-
106
- Returns 204 No Content with Precognition-Success header.
107
- """
108
-
109
- def __init__(self) -> None:
110
- super().__init__(
111
- content=None, status_code=HTTP_204_NO_CONTENT, headers={InertiaHeaders.PRECOGNITION_SUCCESS.value: "true"}
112
- )
113
-
114
-
115
- def create_precognition_exception_handler(
116
- *, fallback_handler: "Callable[[Request[Any, Any, Any], ValidationException], Response[Any]] | None" = None
117
- ) -> "Callable[[Request[Any, Any, Any], ValidationException], Response[Any]]":
118
- """Create an exception handler for ValidationException that supports Precognition.
119
-
120
- This handler checks if the request is a Precognition request and returns
121
- errors in Laravel's format. For non-Precognition requests, it either uses
122
- the fallback handler or returns Litestar's default format.
123
-
124
- Args:
125
- fallback_handler: Optional handler for non-Precognition requests.
126
- If not provided, returns a standard JSON error response.
127
-
128
- Returns:
129
- An exception handler function suitable for Litestar's exception_handlers.
130
- """
131
-
132
- def handler(request: "Request[Any, Any, Any]", exc: ValidationException) -> "Response[Any]":
133
- # Check if this is a Precognition request
134
- precognition_header = request.headers.get(InertiaHeaders.PRECOGNITION.value.lower())
135
- is_precognition = precognition_header == "true"
136
-
137
- if is_precognition:
138
- # Get validate_only fields for partial validation
139
- validate_only_header = request.headers.get(InertiaHeaders.PRECOGNITION_VALIDATE_ONLY.value.lower())
140
- validate_only = (
141
- {field.strip() for field in validate_only_header.split(",") if field.strip()}
142
- if validate_only_header
143
- else None
144
- )
145
-
146
- # Normalize errors to Laravel format
147
- error_data = normalize_validation_errors(exc, validate_only)
148
-
149
- # If filtering removed all errors, return success (204)
150
- if validate_only and not error_data["errors"]:
151
- return PrecognitionResponse()
152
-
153
- return Response(
154
- content=error_data,
155
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
156
- media_type=MediaType.JSON,
157
- headers={InertiaHeaders.PRECOGNITION.value: "true"},
158
- )
159
-
160
- # Non-Precognition request - use fallback or default
161
- if fallback_handler is not None:
162
- return fallback_handler(request, exc)
163
-
164
- # Default Litestar-style error response
165
- return Response(
166
- content={
167
- "status_code": HTTP_422_UNPROCESSABLE_ENTITY,
168
- "detail": "Validation failed",
169
- "extra": exc.detail or [],
170
- },
171
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
172
- media_type=MediaType.JSON,
173
- )
174
-
175
- return handler
176
-
177
-
178
- def precognition(fn: "Callable[..., Any]") -> "Callable[..., Any]":
179
- """Decorator to enable Precognition on a route handler.
180
-
181
- When a Precognition request passes DTO validation, this decorator
182
- returns a 204 No Content response instead of executing the handler body.
183
- This prevents side effects (database writes, emails, etc.) during validation.
184
-
185
- Args:
186
- fn: The route handler function to wrap.
187
-
188
- Returns:
189
- A wrapped handler that short-circuits on valid Precognition requests.
190
-
191
- Example:
192
- ```python
193
- from litestar import post
194
- from litestar_vite.inertia import precognition, InertiaRedirect
195
-
196
- @post("/users")
197
- @precognition
198
- async def create_user(data: UserDTO) -> InertiaRedirect:
199
- # This only runs for actual form submissions
200
- # Precognition validation requests return 204 automatically
201
- user = await User.create(**data.dict())
202
- return InertiaRedirect(request, f"/users/{user.id}")
203
- ```
204
-
205
- Note:
206
- - Validation errors are handled by the exception handler (automatic)
207
- - This decorator handles the success case (prevents handler execution)
208
- - The decorator checks for Precognition header AFTER DTO validation
209
- """
210
- import asyncio
211
-
212
- @wraps(fn)
213
- def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
214
- # Find the request object in args or kwargs
215
- request = _find_request(args, kwargs) # pyright: ignore[reportUnknownVariableType]
216
-
217
- if request is not None:
218
- # Check for Precognition header
219
- precognition_header = request.headers.get(InertiaHeaders.PRECOGNITION.value.lower())
220
- if precognition_header == "true":
221
- # Validation passed (we got here), return success
222
- return PrecognitionResponse()
223
-
224
- # Not a Precognition request, run handler normally
225
- return fn(*args, **kwargs)
226
-
227
- @wraps(fn)
228
- async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
229
- # Find the request object in args or kwargs
230
- request = _find_request(args, kwargs) # pyright: ignore[reportUnknownVariableType]
231
-
232
- if request is not None:
233
- # Check for Precognition header
234
- precognition_header = request.headers.get(InertiaHeaders.PRECOGNITION.value.lower())
235
- if precognition_header == "true":
236
- # Validation passed (we got here), return success
237
- return PrecognitionResponse()
238
-
239
- # Not a Precognition request, run handler normally
240
- result = fn(*args, **kwargs)
241
- if asyncio.iscoroutine(result):
242
- return await result
243
- return result
244
-
245
- # Return appropriate wrapper based on function type
246
- if asyncio.iscoroutinefunction(fn):
247
- return async_wrapper
248
- return sync_wrapper
249
-
250
-
251
- def _find_request(args: tuple[Any, ...], kwargs: "dict[str, Any]") -> "Request[Any, Any, Any] | None": # pyright: ignore[reportUnknownParameterType]
252
- """Find Request object in function arguments.
253
-
254
- Args:
255
- args: Positional arguments to the handler.
256
- kwargs: Keyword arguments to the handler.
257
-
258
- Returns:
259
- The Request object if found, otherwise None.
260
- """
261
- from litestar import Request
262
-
263
- # Check kwargs first (named 'request' parameter)
264
- if "request" in kwargs:
265
- req = kwargs["request"]
266
- if isinstance(req, Request): # pyright: ignore[reportUnknownVariableType]
267
- return req # pyright: ignore[reportUnknownVariableType]
268
-
269
- # Check positional args
270
- for arg in args:
271
- if isinstance(arg, Request): # pyright: ignore[reportUnknownVariableType]
272
- return arg # pyright: ignore[reportUnknownVariableType]
273
-
274
- return None