dynamicforms-fastapi-viewsets 0.6.2__tar.gz → 0.7.0__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.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/.gitignore +0 -1
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/PKG-INFO +1 -1
- dynamicforms_fastapi_viewsets-0.7.0/fastapi_viewsets/__init__.py +1 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/decorators/build_schema.py +9 -6
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/decorators/celery_viewset/client.py +5 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/decorators/celery_viewset/server.py +30 -8
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/decorators/route_viewset.py +6 -4
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/middleware/__init__.py +26 -0
- dynamicforms_fastapi_viewsets-0.6.2/fastapi_viewsets/__init__.py +0 -1
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/LICENSE +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/README.md +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/action_configuration.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/backends/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/backends/django_orm.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/collection_viewset.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/conf.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/context/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/context/auth/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/context/auth/django.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/context/auth/jwt.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/context/auth/static.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/cursor.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/decorators/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/decorators/celery_viewset/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/decorators/celery_viewset/result_reader.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/decorators/conftest.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/decorators/lifecycle_runner.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/decorators/primary_key_model_helper.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/endpoint_docs.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/exceptions.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/filters/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/filters/base.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/filters/declaration.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/filters/operators.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/filters/registry.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/list_query.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/list_shapes.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/middleware/auth/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/middleware/auth/authorization.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/middleware/rate_limiter.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/mixins.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/mux_ws/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/mux_ws/asgi.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/mux_ws/protocol.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/mux_ws/registry.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/mux_ws/server.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/mux_ws/transports.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/py.typed +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/response_classes.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/save_state/__init__.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/save_state/save_state.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/fastapi_viewsets/save_state/serialize_state.py +0 -0
- {dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: dynamicforms-fastapi-viewsets
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
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.0"
|
|
@@ -94,12 +94,15 @@ def build_schema(cls, base_path: str = "", default_tags=None, get_wrapper=None,
|
|
|
94
94
|
and attaches them to the class. Idempotent: if already called, returns immediately.
|
|
95
95
|
If get_wrapper is None, only __router is built (no FastAPI app, no endpoint wrapping).
|
|
96
96
|
|
|
97
|
-
disable_response_model: pass True when command middleware
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
disable_response_model: pass True when a configured command middleware declares
|
|
98
|
+
modifies_response_shape=True (see Middleware.modifies_response_shape and
|
|
99
|
+
any_modifies_response_shape() in fastapi_viewsets/middleware/__init__.py, and route_viewset.py) -
|
|
100
|
+
such a middleware can change the shape of what's actually served (e.g. strip an internal-only
|
|
101
|
+
field via ViewSetResult.body), so FastAPI must not coerce/re-validate the endpoint's return value
|
|
102
|
+
against the ORIGINAL method's declared return type, which would silently undo those changes
|
|
103
|
+
(missing fields reappear with their model defaults). Middleware that doesn't declare this (the
|
|
104
|
+
default) leaves the endpoint's declared return type in place, both in the OpenAPI docs and for
|
|
105
|
+
FastAPI's own response validation.
|
|
103
106
|
"""
|
|
104
107
|
if hasattr(cls, "__router") and (get_wrapper is None or getattr(cls, "__router_full", False)):
|
|
105
108
|
return
|
|
@@ -126,6 +126,11 @@ def _patch_method(cls: type, original_endpoint, task_name: str, celery_app, queu
|
|
|
126
126
|
"_result_queue_key": queue_key,
|
|
127
127
|
**extra_kwargs,
|
|
128
128
|
},
|
|
129
|
+
# send_task() defaults ignore_result to False regardless of what the task was
|
|
130
|
+
# registered with (celery_viewset_server sets ignore_result=True) - Celery's
|
|
131
|
+
# per-request value overrides the task's own, so it must be repeated here or the
|
|
132
|
+
# worker still tries to JSON-encode the raw return value into its result backend.
|
|
133
|
+
ignore_result=True,
|
|
129
134
|
)
|
|
130
135
|
return await future
|
|
131
136
|
except Exception:
|
|
@@ -59,6 +59,26 @@ def _to_jsonable(value):
|
|
|
59
59
|
return value
|
|
60
60
|
|
|
61
61
|
|
|
62
|
+
def _reconstruct_value(hint, value):
|
|
63
|
+
"""Rebuild a dict (or list/tuple of dicts) back into BaseModel instance(s) per `hint`.
|
|
64
|
+
|
|
65
|
+
`_reconstruct_kwargs` only unwraps `Optional[...]` at the top level, so a hint arriving here
|
|
66
|
+
may still be `list[Model]` or `Optional[list[Model]]` - handled by recursing into the origin's
|
|
67
|
+
type args rather than requiring `hint` itself to be a bare BaseModel subclass.
|
|
68
|
+
"""
|
|
69
|
+
hint = _unwrap_optional(hint)
|
|
70
|
+
if hint is not None and isinstance(value, dict) and inspect.isclass(hint) and issubclass(hint, BaseModel):
|
|
71
|
+
try:
|
|
72
|
+
return hint.model_validate(value)
|
|
73
|
+
except Exception:
|
|
74
|
+
return hint.model_construct(**value)
|
|
75
|
+
if get_origin(hint) in (list, tuple) and isinstance(value, (list, tuple)):
|
|
76
|
+
args = get_args(hint)
|
|
77
|
+
if args:
|
|
78
|
+
return [_reconstruct_value(args[0], item) for item in value]
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
|
|
62
82
|
def _reconstruct_kwargs(original_endpoint, kwargs: dict, cls: type = None) -> dict:
|
|
63
83
|
"""Reconstruct dict values back into Pydantic BaseModel instances based on endpoint type hints.
|
|
64
84
|
|
|
@@ -85,14 +105,8 @@ def _reconstruct_kwargs(original_endpoint, kwargs: dict, cls: type = None) -> di
|
|
|
85
105
|
for key, value in remaining.items():
|
|
86
106
|
hint = hints.get(key)
|
|
87
107
|
if hint is not None:
|
|
88
|
-
hint =
|
|
89
|
-
|
|
90
|
-
try:
|
|
91
|
-
result[key] = hint.model_validate(value)
|
|
92
|
-
except Exception:
|
|
93
|
-
result[key] = hint.model_construct(**value)
|
|
94
|
-
else:
|
|
95
|
-
result[key] = value
|
|
108
|
+
hint = resolve_typevars(type_map, hint)
|
|
109
|
+
result[key] = _reconstruct_value(hint, value)
|
|
96
110
|
return result
|
|
97
111
|
|
|
98
112
|
|
|
@@ -144,6 +158,14 @@ def celery_viewset_server(
|
|
|
144
158
|
}
|
|
145
159
|
),
|
|
146
160
|
)
|
|
161
|
+
# celery_viewset_client never reads this task's own retval (only the queue
|
|
162
|
+
# entry just pushed above), so returning `result` a second time here would
|
|
163
|
+
# hand Celery a raw endpoint return value - e.g. a Pydantic model - to
|
|
164
|
+
# encode on its own (result backend, task-succeeded event, ...), which
|
|
165
|
+
# isn't JSON-safe. ignore_result=True (set on both task registration and
|
|
166
|
+
# send_task) tells Celery to skip that, but a JSON-trivial retval here means
|
|
167
|
+
# nothing breaks even where that setting doesn't reach.
|
|
168
|
+
return True
|
|
147
169
|
return result
|
|
148
170
|
except Exception as e:
|
|
149
171
|
if isinstance(e, HTTPException):
|
|
@@ -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 Middleware
|
|
24
|
+
from fastapi_viewsets.middleware import any_modifies_response_shape, Middleware
|
|
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
|
|
|
@@ -342,14 +342,16 @@ def route_viewset(
|
|
|
342
342
|
return wrapper, new_return_annotation
|
|
343
343
|
|
|
344
344
|
# Command middleware (see fastapi_viewsets/middleware.py) can reshape the response body via
|
|
345
|
-
# ViewSetResult -
|
|
346
|
-
#
|
|
345
|
+
# ViewSetResult - only middleware that actually declares modifies_response_shape=True
|
|
346
|
+
# untrusts the endpoint's declared return type as the response_model (same reasoning as the
|
|
347
|
+
# old finalize_response hook); typical middleware (auth, rate limiting, sessions) only adds
|
|
348
|
+
# headers/cookies and leaves the declared model - and therefore the OpenAPI docs - in place.
|
|
347
349
|
build_schema(
|
|
348
350
|
cls,
|
|
349
351
|
base_path,
|
|
350
352
|
default_tags,
|
|
351
353
|
get_wrapper,
|
|
352
|
-
disable_response_model=
|
|
354
|
+
disable_response_model=any_modifies_response_shape(settings.viewsets_command_middleware),
|
|
353
355
|
)
|
|
354
356
|
|
|
355
357
|
muxws_routes = []
|
|
@@ -57,6 +57,20 @@ class Middleware(ABC):
|
|
|
57
57
|
concrete example) - a bare function would need extra indirection (closures, partials) for that.
|
|
58
58
|
"""
|
|
59
59
|
|
|
60
|
+
modifies_response_shape: bool = False
|
|
61
|
+
"""
|
|
62
|
+
Whether this middleware can change the shape of ViewSetResult.body (strip/rename/restructure
|
|
63
|
+
fields, not just add side-channel headers/cookies or a status code) - see
|
|
64
|
+
`any_modifies_response_shape()` below. False by default: typical middleware (auth, rate
|
|
65
|
+
limiting, sessions) only inspects the result or attaches headers/cookies, so the endpoint's
|
|
66
|
+
declared return type stays a trustworthy response_model. Override to True on a middleware whose
|
|
67
|
+
__call__ actually reassigns `result.body` to something that no longer matches the original
|
|
68
|
+
return annotation - otherwise FastAPI's response_model validation will coerce the reshaped body
|
|
69
|
+
back towards the original model (e.g. reintroducing a stripped field with its default value).
|
|
70
|
+
A plain function middleware declares this the same way, by setting the attribute on the
|
|
71
|
+
function object: `my_middleware.modifies_response_shape = True`.
|
|
72
|
+
"""
|
|
73
|
+
|
|
60
74
|
def config_from(self, context: Context) -> Any:
|
|
61
75
|
"""
|
|
62
76
|
This middleware's own @action_configuration value for the current call (see
|
|
@@ -104,6 +118,18 @@ class Middleware(ABC):
|
|
|
104
118
|
raise NotImplementedError
|
|
105
119
|
|
|
106
120
|
|
|
121
|
+
def any_modifies_response_shape(middlewares: list) -> bool:
|
|
122
|
+
"""
|
|
123
|
+
Whether any entry in `middlewares` (a mix of Middleware instances and/or plain functions, as
|
|
124
|
+
settings.viewsets_command_middleware holds) declares modifies_response_shape=True. A bare
|
|
125
|
+
function that never set the attribute is treated as False, same default as the Middleware
|
|
126
|
+
class - see Middleware.modifies_response_shape above. Used by route_viewset/build_schema to
|
|
127
|
+
decide whether an endpoint's declared response_model can still be trusted for OpenAPI docs and
|
|
128
|
+
FastAPI's own response validation.
|
|
129
|
+
"""
|
|
130
|
+
return any(getattr(middleware, "modifies_response_shape", False) for middleware in middlewares)
|
|
131
|
+
|
|
132
|
+
|
|
107
133
|
async def run_command_chain(
|
|
108
134
|
middlewares: list[CommandMiddleware],
|
|
109
135
|
request: "Request | None",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.6.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dynamicforms_fastapi_viewsets-0.6.2 → dynamicforms_fastapi_viewsets-0.7.0}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|