dynamicforms-fastapi-viewsets 0.7.0__tar.gz → 0.7.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.
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/PKG-INFO +1 -1
- dynamicforms_fastapi_viewsets-0.7.1/fastapi_viewsets/__init__.py +1 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/context/__init__.py +40 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/decorators/build_schema.py +6 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/decorators/celery_viewset/client.py +6 -7
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/decorators/celery_viewset/result_reader.py +5 -1
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/decorators/celery_viewset/server.py +7 -8
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/decorators/lifecycle_runner.py +5 -1
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/decorators/route_viewset.py +3 -2
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/middleware/__init__.py +52 -4
- dynamicforms_fastapi_viewsets-0.7.0/fastapi_viewsets/__init__.py +0 -1
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/.gitignore +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/LICENSE +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/README.md +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/action_configuration.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/backends/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/backends/django_orm.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/collection_viewset.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/conf.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/context/auth/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/context/auth/django.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/context/auth/jwt.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/context/auth/static.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/cursor.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/decorators/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/decorators/celery_viewset/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/decorators/conftest.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/decorators/primary_key_model_helper.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/endpoint_docs.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/exceptions.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/filters/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/filters/base.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/filters/declaration.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/filters/operators.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/filters/registry.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/list_query.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/list_shapes.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/middleware/auth/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/middleware/auth/authorization.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/middleware/rate_limiter.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/mixins.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/mux_ws/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/mux_ws/asgi.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/mux_ws/protocol.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/mux_ws/registry.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/mux_ws/server.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/mux_ws/transports.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/py.typed +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/response_classes.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/save_state/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/save_state/save_state.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/save_state/serialize_state.py +0 -0
- {dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: dynamicforms-fastapi-viewsets
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Django REST Framework-style viewsets for FastAPI, with optional Celery-backed async execution and a Vue/TypeScript client counterpart.
|
|
5
5
|
Project-URL: Homepage, https://github.com/dynamicforms/fastapi-viewsets
|
|
6
6
|
Project-URL: Repository, https://github.com/dynamicforms/fastapi-viewsets
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.7.1"
|
|
@@ -6,6 +6,8 @@ import json
|
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
7
|
from typing import Any, TYPE_CHECKING
|
|
8
8
|
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
9
11
|
from ..conf import settings
|
|
10
12
|
|
|
11
13
|
if TYPE_CHECKING:
|
|
@@ -300,6 +302,44 @@ def _resolve_class(dotted_path: str) -> type[SerializableObject]:
|
|
|
300
302
|
return cls
|
|
301
303
|
|
|
302
304
|
|
|
305
|
+
def serialize_value(value: Any) -> Any:
|
|
306
|
+
"""
|
|
307
|
+
Tag a single SerializableObject for JSON transport, the same convention serialize_context()
|
|
308
|
+
applies per context key (see _TYPE_KEY/_VALUE_KEY above); anything else passes through
|
|
309
|
+
unchanged. For a value crossing the Celery/Redis boundary outside a Context dict - e.g. a
|
|
310
|
+
command-middleware ViewSetResult returned by a celery_viewset-dispatched action (see
|
|
311
|
+
fastapi_viewsets.middleware.ViewSetResult).
|
|
312
|
+
"""
|
|
313
|
+
if isinstance(value, SerializableObject):
|
|
314
|
+
return {_TYPE_KEY: _class_tag(type(value)), _VALUE_KEY: value.__serialize__()}
|
|
315
|
+
return value
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def to_jsonable(value: Any) -> Any:
|
|
319
|
+
"""
|
|
320
|
+
Recursively convert a value into a JSON-safe structure: a SerializableObject is tagged (see
|
|
321
|
+
serialize_value()), a Pydantic model is dumped, a list/tuple is recursed into element-wise -
|
|
322
|
+
anything else passes through unchanged. The one shared implementation of a conversion several
|
|
323
|
+
call sites need on their own single (non-Context, non-dict) return/body value - ViewSetResult's
|
|
324
|
+
own (de)serialization, and a celery_viewset worker's task result - rather than each keeping a
|
|
325
|
+
slightly different copy that drifts out of sync with what the others handle.
|
|
326
|
+
"""
|
|
327
|
+
if isinstance(value, SerializableObject):
|
|
328
|
+
return serialize_value(value)
|
|
329
|
+
if isinstance(value, BaseModel):
|
|
330
|
+
return value.model_dump(mode="json")
|
|
331
|
+
if isinstance(value, (list, tuple)):
|
|
332
|
+
return [to_jsonable(v) for v in value]
|
|
333
|
+
return value
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def deserialize_value(value: Any) -> Any:
|
|
337
|
+
"""Inverse of serialize_value() - passes through anything that isn't a tagged payload."""
|
|
338
|
+
if isinstance(value, dict) and _TYPE_KEY in value and _VALUE_KEY in value:
|
|
339
|
+
return _resolve_class(value[_TYPE_KEY]).__deserialize__(value[_VALUE_KEY])
|
|
340
|
+
return value
|
|
341
|
+
|
|
342
|
+
|
|
303
343
|
async def serialize_context(data: dict[str, Any]) -> dict[str, Any]:
|
|
304
344
|
"""
|
|
305
345
|
Returns a JSON-safe dict: SerializableObject values are swapped for a tagged payload (see
|
|
@@ -6,6 +6,7 @@ from fastapi import APIRouter, FastAPI
|
|
|
6
6
|
from fastapi.routing import APIRoute
|
|
7
7
|
|
|
8
8
|
from ..endpoint_docs import docs_for, viewset_tags
|
|
9
|
+
from ..middleware import unwrap_viewset_result_type
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def _generic_args(annotation) -> tuple:
|
|
@@ -166,6 +167,11 @@ def build_schema(cls, base_path: str = "", default_tags=None, get_wrapper=None,
|
|
|
166
167
|
# answer: only route_viewset knows what this viewset bound it to.
|
|
167
168
|
use_resolved = route.response_model is None or has_typevars(route.response_model)
|
|
168
169
|
response_model = resolved_response_model if use_resolved else route.response_model
|
|
170
|
+
# route.response_model is FastAPI's own inference from the ORIGINAL declared return
|
|
171
|
+
# type (e.g. -> ViewSetResult[Item]), untouched by get_wrapper's own unwrap - must be
|
|
172
|
+
# unwrapped here too, or an endpoint declared this way would document/validate as the
|
|
173
|
+
# wrapper instead of the wire body whenever use_resolved is False.
|
|
174
|
+
response_model = unwrap_viewset_result_type(response_model)
|
|
169
175
|
|
|
170
176
|
# Per-viewset wording, applied here because this is where the schema-serving app is
|
|
171
177
|
# built; patching the caller's router afterwards would leave /schema showing the
|
|
@@ -6,9 +6,7 @@ from collections.abc import Awaitable, Callable
|
|
|
6
6
|
from functools import wraps
|
|
7
7
|
from typing import TYPE_CHECKING, TypeVar
|
|
8
8
|
|
|
9
|
-
from
|
|
10
|
-
|
|
11
|
-
from ...context import Context, serialize_context
|
|
9
|
+
from ...context import Context, serialize_context, to_jsonable
|
|
12
10
|
from ..build_schema import build_schema
|
|
13
11
|
from . import result_reader
|
|
14
12
|
from .result_reader import get_result_queue_key
|
|
@@ -84,16 +82,17 @@ def celery_viewset_client(
|
|
|
84
82
|
|
|
85
83
|
|
|
86
84
|
async def _serialize_value(value):
|
|
87
|
-
"""Convert a Context,
|
|
85
|
+
"""Convert a Context, dict, or list/tuple value into a JSON-safe structure for Celery/Kombu
|
|
86
|
+
transport, recursing async since Context serialization needs to await context values. Anything
|
|
87
|
+
else - a Pydantic model, a SerializableObject, or an already JSON-native value - is handled by
|
|
88
|
+
the shared, synchronous fastapi_viewsets.context.to_jsonable()."""
|
|
88
89
|
if isinstance(value, Context):
|
|
89
90
|
return await serialize_context(value.raw())
|
|
90
|
-
if isinstance(value, BaseModel):
|
|
91
|
-
return value.model_dump(mode="json")
|
|
92
91
|
if isinstance(value, dict):
|
|
93
92
|
return {k: await _serialize_value(v) for k, v in value.items()}
|
|
94
93
|
if isinstance(value, (list, tuple)):
|
|
95
94
|
return [await _serialize_value(v) for v in value]
|
|
96
|
-
return value
|
|
95
|
+
return to_jsonable(value)
|
|
97
96
|
|
|
98
97
|
|
|
99
98
|
def _patch_method(cls: type, original_endpoint, task_name: str, celery_app, queue_key: str):
|
|
@@ -6,6 +6,8 @@ from typing import TYPE_CHECKING
|
|
|
6
6
|
|
|
7
7
|
from fastapi import HTTPException
|
|
8
8
|
|
|
9
|
+
from ...context import deserialize_value
|
|
10
|
+
|
|
9
11
|
if TYPE_CHECKING:
|
|
10
12
|
import redis
|
|
11
13
|
|
|
@@ -59,7 +61,9 @@ async def result_reader_loop(redis_client: "redis.Redis", queue_key: str, poll_i
|
|
|
59
61
|
else:
|
|
60
62
|
future.set_exception(Exception(error))
|
|
61
63
|
else:
|
|
62
|
-
|
|
64
|
+
# Undoes _to_jsonable's SerializableObject tagging (see server.py) - a plain
|
|
65
|
+
# domain result has no _TYPE_KEY/_VALUE_KEY pair and passes through unchanged.
|
|
66
|
+
future.set_result(deserialize_value(result))
|
|
63
67
|
|
|
64
68
|
except asyncio.CancelledError:
|
|
65
69
|
break
|
|
@@ -11,7 +11,7 @@ from typing import get_args, get_origin, get_type_hints, TYPE_CHECKING, TypeVar,
|
|
|
11
11
|
from fastapi import HTTPException
|
|
12
12
|
from pydantic import BaseModel
|
|
13
13
|
|
|
14
|
-
from ...context import Context, deserialize_context
|
|
14
|
+
from ...context import Context, deserialize_context, to_jsonable
|
|
15
15
|
from ..build_schema import build_schema
|
|
16
16
|
from ..lifecycle_runner import lifecycle_runner, LifecycleType
|
|
17
17
|
from ..route_viewset import build_type_map, resolve_typevars
|
|
@@ -50,13 +50,12 @@ def _unwrap_optional(hint):
|
|
|
50
50
|
return hint
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return value
|
|
53
|
+
# Alias, not a re-implementation: the actual conversion (Pydantic models, lists/tuples,
|
|
54
|
+
# SerializableObject values - e.g. a ViewSetResult a worker-side action returned directly, see
|
|
55
|
+
# fastapi_viewsets.middleware) lives in fastapi_viewsets.context.to_jsonable, shared with
|
|
56
|
+
# ViewSetResult's own (de)serialization. Kept under this name since call sites/tests here already
|
|
57
|
+
# import it as `_to_jsonable`.
|
|
58
|
+
_to_jsonable = to_jsonable
|
|
60
59
|
|
|
61
60
|
|
|
62
61
|
def _reconstruct_value(hint, value):
|
|
@@ -68,7 +68,11 @@ async def lifecycle_runner(
|
|
|
68
68
|
return await call_bound(req_instance)
|
|
69
69
|
|
|
70
70
|
async def final_handler() -> ViewSetResult:
|
|
71
|
-
|
|
71
|
+
raw = await call_bound(req_instance)
|
|
72
|
+
# An endpoint may return a ViewSetResult directly (see ViewSetResult in
|
|
73
|
+
# fastapi_viewsets/middleware) to reach status_code/headers/cookies itself - passed
|
|
74
|
+
# through as-is.
|
|
75
|
+
return raw if isinstance(raw, ViewSetResult) else ViewSetResult(body=raw)
|
|
72
76
|
|
|
73
77
|
effective_middlewares = settings.viewsets_command_middleware + extra_middlewares_for(
|
|
74
78
|
action_configuration, settings.viewsets_command_middleware
|
|
@@ -21,7 +21,7 @@ from fastapi_viewsets.endpoint_docs import (
|
|
|
21
21
|
register_tag,
|
|
22
22
|
viewset_description,
|
|
23
23
|
)
|
|
24
|
-
from fastapi_viewsets.middleware import any_modifies_response_shape, Middleware
|
|
24
|
+
from fastapi_viewsets.middleware import any_modifies_response_shape, Middleware, unwrap_viewset_result_type
|
|
25
25
|
from fastapi_viewsets.mixins import FilterParam
|
|
26
26
|
from fastapi_viewsets.mux_ws import register_viewset, resolve_register_muxws, resolve_register_rest
|
|
27
27
|
|
|
@@ -298,7 +298,8 @@ def route_viewset(
|
|
|
298
298
|
# right before the endpoint runs - never forwarded to the endpoint itself.
|
|
299
299
|
new_params.append(inspect.Parameter("request", inspect.Parameter.KEYWORD_ONLY, annotation=Request))
|
|
300
300
|
|
|
301
|
-
|
|
301
|
+
# -> ViewSetResult[X] documents/validates as X - see unwrap_viewset_result_type.
|
|
302
|
+
new_return_annotation = unwrap_viewset_result_type(resolve_typevars(type_map, sig.return_annotation))
|
|
302
303
|
new_sig = sig.replace(parameters=new_params, return_annotation=new_return_annotation)
|
|
303
304
|
|
|
304
305
|
@wraps(original_endpoint)
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from collections.abc import Awaitable, Callable
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
|
-
from typing import Any, TYPE_CHECKING
|
|
4
|
+
from typing import Any, Generic, get_args, get_origin, TYPE_CHECKING, TypeVar
|
|
5
5
|
|
|
6
|
-
from ..context import Context
|
|
6
|
+
from ..context import Context, SerializableObject, to_jsonable
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from fastapi import Request
|
|
10
10
|
|
|
11
|
+
T = TypeVar("T")
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
@dataclass
|
|
13
|
-
class ViewSetResult:
|
|
15
|
+
class ViewSetResult(SerializableObject, Generic[T]):
|
|
14
16
|
"""
|
|
15
17
|
Transport-agnostic result of running the command middleware chain (see run_command_chain()
|
|
16
18
|
below). `body` is the actual domain value (whatever perform_* returned); `headers`/`cookies`
|
|
@@ -22,13 +24,44 @@ class ViewSetResult:
|
|
|
22
24
|
onto real Set-Cookie, and `status_code` onto the real Response's status code; a future WS
|
|
23
25
|
adapter would fold all three into the outgoing message payload as plain JSON keys instead (no
|
|
24
26
|
real headers/cookies/status line exist over WS).
|
|
27
|
+
|
|
28
|
+
A registered route endpoint (a mixin-provided one, or a custom `__router` method - never a
|
|
29
|
+
`perform_*` hook, which is called by the actual route method and never registered as a route
|
|
30
|
+
itself) may also return a `ViewSetResult` directly instead of a plain body, getting the same
|
|
31
|
+
`status_code`/`headers`/`cookies` access normally reserved for command middleware (e.g. a
|
|
32
|
+
redirect: `ViewSetResult(body=None, status_code=302, headers={"Location": url})`) -
|
|
33
|
+
`lifecycle_runner.final_handler` passes it through unchanged. Declare the return type as
|
|
34
|
+
`ViewSetResult[X]` (X being whatever the wire body actually is) rather than bare `X` -
|
|
35
|
+
`route_viewset`/`build_schema` unwrap this to `X` for the OpenAPI response_model and FastAPI's
|
|
36
|
+
own response validation (see unwrap_viewset_result_type below), so the two forms
|
|
37
|
+
document/validate identically.
|
|
38
|
+
|
|
39
|
+
Inherits `SerializableObject` so it survives the Celery/Redis boundary: a
|
|
40
|
+
`celery_viewset`-dispatched action's worker-side return value is passed through
|
|
41
|
+
`serialize_value()`/`deserialize_value()` (see fastapi_viewsets.context) exactly like any other
|
|
42
|
+
`SerializableObject`, the same mechanism `LazyObject`/`Context` values already rely on. Only
|
|
43
|
+
`__serialize__`/`__deserialize__` are overridden here - a `ViewSetResult` is never itself placed
|
|
44
|
+
in a `Context` and never awaited, so `SerializableObject`'s `value`/`__await__`/`__init__` go
|
|
45
|
+
unused, the same way `LazyObject` replaces those with its own.
|
|
25
46
|
"""
|
|
26
47
|
|
|
27
|
-
body:
|
|
48
|
+
body: T
|
|
28
49
|
headers: dict[str, Any] = field(default_factory=dict)
|
|
29
50
|
cookies: dict[str, Any] = field(default_factory=dict)
|
|
30
51
|
status_code: int | None = None
|
|
31
52
|
|
|
53
|
+
def __serialize__(self) -> Any:
|
|
54
|
+
return {
|
|
55
|
+
"body": to_jsonable(self.body),
|
|
56
|
+
"headers": self.headers,
|
|
57
|
+
"cookies": self.cookies,
|
|
58
|
+
"status_code": self.status_code,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def __deserialize__(cls, data: Any) -> "ViewSetResult":
|
|
63
|
+
return cls(body=data["body"], headers=data["headers"], cookies=data["cookies"], status_code=data["status_code"])
|
|
64
|
+
|
|
32
65
|
|
|
33
66
|
CommandMiddleware = Callable[
|
|
34
67
|
["Request | None", Any, Context, Callable[[], Awaitable[ViewSetResult]]],
|
|
@@ -130,6 +163,21 @@ def any_modifies_response_shape(middlewares: list) -> bool:
|
|
|
130
163
|
return any(getattr(middleware, "modifies_response_shape", False) for middleware in middlewares)
|
|
131
164
|
|
|
132
165
|
|
|
166
|
+
def unwrap_viewset_result_type(annotation: Any) -> Any:
|
|
167
|
+
"""
|
|
168
|
+
An endpoint declared `-> ViewSetResult[X]` (see ViewSetResult above) documents/validates as
|
|
169
|
+
`X` - the wire body a client actually receives, not the side-channel wrapper the endpoint
|
|
170
|
+
returns internally to reach `status_code`/`headers`/`cookies`. `route_viewset`/`build_schema`
|
|
171
|
+
call this wherever a return annotation becomes an OpenAPI response_model, so `-> ViewSetResult[X]`
|
|
172
|
+
and plain `-> X` document/validate identically. Anything that isn't `ViewSetResult[...]` passes
|
|
173
|
+
through unchanged.
|
|
174
|
+
"""
|
|
175
|
+
if get_origin(annotation) is ViewSetResult:
|
|
176
|
+
args = get_args(annotation)
|
|
177
|
+
return args[0] if args else None
|
|
178
|
+
return annotation
|
|
179
|
+
|
|
180
|
+
|
|
133
181
|
async def run_command_chain(
|
|
134
182
|
middlewares: list[CommandMiddleware],
|
|
135
183
|
request: "Request | None",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.7.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dynamicforms_fastapi_viewsets-0.7.0 → dynamicforms_fastapi_viewsets-0.7.1}/fastapi_viewsets/conf.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|