Views¶
Mutation views¶
ServiceCreateView ¶
Bases: MutationFlowMixin, GenericAPIView
POST endpoint that runs a service callable to create a resource.
Configure by setting spec to a
ServiceSpec. The spec's
success_status defaults to 201 Created when unset.
ServiceUpdateView ¶
Bases: MutationFlowMixin, GenericAPIView
PUT / PATCH endpoint that runs a service callable.
The instance to update is resolved via spec.instance_selector_spec
when set — no queryset / lookup_field required on the subclass —
falling back to DRF's get_object() (set queryset and
lookup_field, or override get_object()).
Configure by setting spec to a
ServiceSpec. The spec's
success_status defaults to 200 OK when unset. Both verbs share the one spec,
so a forced spec.partial applies to PUT and PATCH — set http_method_names =
["patch"] for a PATCH-only endpoint.
ServiceDeleteView ¶
Bases: MutationFlowMixin, GenericAPIView
DELETE endpoint that runs a service callable.
The instance to delete is resolved via spec.instance_selector_spec
when set — no queryset / lookup_field required on the subclass —
falling back to DRF's get_object().
Configure by setting spec to a
ServiceSpec. The spec's
input_serializer is optional (for delete-with-payload patterns such as a
deletion reason); success_status defaults to 204 No Content; set
output_selector_spec with an output_serializer on the spec to render a body
instead.
Selector views¶
SelectorListView ¶
Bases: ListModelMixin, GenericAPIView
GET endpoint that delegates to a selector or to get_queryset().
Set spec to a
SelectorSpec to
configure the selector and/or the output serializer. Both fields are optional:
spec.selectoroverridesget_queryset();Nonefalls back to the inheritedquerysetattribute.spec.output_serializeroverridesget_serializer_class();Nonefalls back to DRF's standardserializer_classattribute.
spec = None (the default) keeps both as vanilla DRF.
The rest of the list flow — filter backends, pagination, response
rendering — is the standard DRF ListModelMixin.
get_selector_kwargs ¶
Hook for additional kwargs available to the selector signature.
SelectorRetrieveView ¶
Bases: RetrieveModelMixin, GenericAPIView
GET endpoint that returns a single object.
Set spec to a
SelectorSpec to
configure the selector and/or the output serializer. Both fields are optional:
spec.selectoroverridesget_object();Nonefalls back toself.get_object()— standard DRF lookup usingquerysetandlookup_field. ReturningNoneor raisingModel.DoesNotExistresults in a 404 — or, when the spec setsallow_none=True, a200with a JSONnullbody (the nullable-resource contract; the output serializer is skipped).spec.output_serializeroverridesget_serializer_class();Nonefalls back to DRF's standardserializer_classattribute.
spec = None (the default) keeps both as vanilla DRF.
get_object ¶
Resolve the target row through spec.selector, if one is set.
filter_backends do not apply here. DRF runs
filter_queryset() inside its own get_object(), so overriding
that method — this view, or any hand-written override — drops them.
A tenant-scoping backend in DEFAULT_FILTER_BACKENDS scopes a
sibling list view and not this lookup. Scope the selector's own
queryset, or declare the rule as SelectorSpec.filter_set, which
the dispatcher applies on both the list and the retrieve path.
Mutation flow mixin¶
MutationFlowMixin ¶
Bases: _ServesRaisedSchema
Provides _run_mutation for service-backed views and viewset mixins.
The flow itself lives in dispatch_mutation_for_spec, so
@service_action can reach it without being a class; this mixin is the OO
entry point that the per-action mixins (ServiceCreateMixin and friends)
and the standalone single-purpose views compose, calling
self._run_mutation(...) after resolving their per-action spec.
Four hook chains feed a mutation, each layered view-wide → per-action →
per-spec and merged with dict.update so the more specific hook wins on
overlapping keys:
- extra service kwargs:
get_service_kwargs→get_<action>_service_kwargs→ServiceSpec.kwargs. - the serializer's input dict, merged on top of
request.databefore validation:get_input_data→get_<action>_input_data→ServiceSpec.input_data. - the input serializer's
context=:get_serializer_context(DRF's own) →get_input_serializer_context→get_<action>_input_serializer_context→ServiceSpec.input_serializer_context. - the output serializer's
context=: the same chain withoutputin place ofinput, applied during response rendering.
The per-action layer reads self.action, so it applies to viewsets only.
get_service_kwargs ¶
Hook for additional kwargs available to every mutation service.
get_input_data ¶
Hook for extras merged on top of request.data before validation.
get_input_serializer_context ¶
Hook for the context= dict passed to the input serializer.
Defaults to get_serializer_context, so overriding the
DRF-standard hook flows into input validation automatically; override
here for keys visible only during input validation.
get_output_serializer_context ¶
Hook for the context= dict passed to the output serializer.
Defaults to get_serializer_context, so overriding the
DRF-standard hook flows into response rendering automatically; override
here for keys visible only during response rendering.
get_permissions ¶
Honor spec.permission_classes on standalone mutation views.
Standalone Service*View subclasses carry spec as a class
attribute; when it sets permission_classes those win over the view's
class-level ones, and None falls through.
ServiceView Protocol¶
ServiceView ¶
Bases: Protocol
Minimal structural shape of a view as exposed to a kwargs provider.
Per-spec kwargs providers (ServiceSpec.kwargs / SelectorSpec.kwargs) receive
the calling view typed as
ServiceView. The
Protocol pins only the attributes a provider can rely on across both the standalone
Service*View classes and the viewset mixins:
request— the current DRFRequest.kwargs— the URL kwargs dict resolved by the URLconf (e.g.{"pk": 7}). Empty dict on routes without captured groups.action— the viewset action name ("create","list", etc.) on viewsets;Noneon standalone single-purpose views.
Keep the surface narrow on purpose: providers should translate view state into typed kwargs for the service, not reach for view internals.
OfflineServiceView¶
OfflineServiceView
dataclass
¶
A concrete
ServiceView for
dispatching a spec outside an HTTP request.
Spec kwargs / extend_queryset / context providers are typed against
ServiceView — the
structural request / kwargs / action surface. When a spec is
dispatched off the HTTP path (a Pydantic-AI toolset, the MCP server, a
management command) there is no DRF view, so this frozen value stands in:
request— the synthetic DRFRequestbuilt bybuild_offline_context.action— an optional label identifying the operation;Nonewhen the caller has no meaningful action name.kwargs— the URL-kwargs equivalent (e.g. parent ids), empty by default.
Kwarg resolution¶
utils ¶
Cross-cutting view helpers used by both mutation and query views.
resolve_extra_kwargs ¶
resolve_extra_kwargs(
view: Any,
request: Request,
*,
spec_kwargs: Callable[..., dict[str, Any]] | None,
action_hook: str | None,
catch_all_hook: str,
) -> dict[str, Any]
Collect the extras that should be merged into a service/selector pool.
Three layers, merged with dict.update in increasing specificity so the
spec-level provider has the final say on overlapping keys. Each is invoked
through the framework's provider convention (_invoke_provider),
receiving only the subset of {view, request, call_context} it declares,
or {view, request} via **kwargs: the call's context is offered by
name only.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec_kwargs
|
Callable[..., dict[str, Any]] | None
|
The spec's own |
required |
action_hook
|
str | None
|
Per-action view method, e.g. |
required |
catch_all_hook
|
str
|
View-wide fallback, e.g. |
required |
resolve_input_extras ¶
resolve_input_extras(
view: Any,
request: Request,
*,
spec_input_data: Callable[..., Mapping[str, Any]] | None,
action_hook: str | None,
catch_all_hook: str,
extras: Mapping[str, Any] | None = None,
) -> dict[str, Any]
Collect the extras to merge into the serializer input dict.
resolve_extra_kwargs's layering — catch_all_hook view method,
then action_hook, then the spec's ServiceSpec.input_data
provider — applied to the input_serializer-bound data rather than the
service-call pool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
extras
|
Mapping[str, Any] | None
|
The resolved data available before validation — currently the
mutation target |
None
|
view_call_context ¶
The call's context of the request view is serving: one dict per request.
Built on first use with the edge's four standard entries (request,
view, format None and the view's action or None) and kept
on the view, which DRF builds afresh for every request, so a second request
is a second dict (test_each_request_is_a_call_of_its_own). Kept rather than
rebuilt: a dict per caller fails the identity assertions of
test_a_create_hands_every_provider_the_request_s_context. Two dispatches in
one request, the retrieve spec finding an update's row and then the update
spec, are one call and share it
(test_an_update_and_the_retrieve_that_finds_its_row_share_the_request_s_dict).
A view hands this object to dispatch_spec as its call_context= and
to render_spec_output, so the spec's own callables are offered it as
the edge offers them, and it offers it itself to each callable it invokes:
- the hooks
get_service_kwargs/get_selector_kwargs,get_input_data,get_input_serializer_contextandget_output_serializer_context, and the per-action form of each (get_<action>_service_kwargsand so on); - the spec's
*_serializer_contextprovider where the view builds the serializer context itself; get_progress_reporterandget_<action>_progress_reporter;- a
PolymorphicServiceSpec'sdiscriminator.
Each is offered it by name only, as the kernel binds it: a callable
declaring a call_context parameter is handed this dict, and one taking
**kwargs alone never is, so its keys, and a serializer context built
from them, carry no call_context
(test_a_catch_all_callable_on_a_live_view_is_never_handed_the_call_context).
success_status and response_finalizer are not offered it: a view
refuses either naming it when the view is built.
layer_serializer_context ¶
layer_serializer_context(
base: Mapping[str, Any],
view: Any,
request: Request,
*,
direction_hook: str | None,
action_hook: str | None,
spec_provider: Callable[..., Mapping[str, Any]] | None = None,
extras: Mapping[str, Any] | None = None,
) -> dict[str, Any]
Layer the directional, action, and spec context hooks onto base.
resolve_serializer_context's precedence rules with layer 1 handed in
rather than read from view.get_serializer_context() — for overrides of
that method which need to extend super()'s result without recursing. See
there for action_hook / spec_provider / extras.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base
|
Mapping[str, Any]
|
The layer-1 context to build on. |
required |
direction_hook
|
str | None
|
Directional view hook name; |
required |
resolve_serializer_context ¶
resolve_serializer_context(
view: Any,
request: Request,
*,
direction_hook: str,
action_hook: str | None,
spec_provider: Callable[..., Mapping[str, Any]] | None = None,
extras: Mapping[str, Any] | None = None,
) -> dict[str, Any]
Build the serializer context dict for one direction (input or output).
Four layers, merged with dict.update in increasing specificity so the
spec-level provider has the final say on overlapping keys:
view.get_serializer_context() (DRF's own) → view.<direction_hook>()
→ view.<action_hook>() → spec_provider. An absent view method skips
its layer, so plain DRF viewsets work unchanged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
direction_hook
|
str
|
|
required |
action_hook
|
str | None
|
Per-action override, e.g.
|
required |
spec_provider
|
Callable[..., Mapping[str, Any]] | None
|
The spec's own |
None
|
extras
|
Mapping[str, Any] | None
|
The resolved data about to be serialized (a mutation's
|
None
|
get_class_attr ¶
Return the named class attribute without instance binding.
A function stored as a plain class attribute (service = my_fn) would
otherwise be wrapped in a bound method when read via self.
resolve_callable_kwargs ¶
Pick the subset of pool matching fn's declared parameters.
If fn declares **kwargs, the entire pool is passed.
Otherwise only parameters present in the signature are forwarded.
request_seeds ¶
The pool seeds an HTTP request carries: request, user and, where DRF
recorded one, view.
What call_service, call_selector and their async twins seed, so a
callable they invoke from inside a view sees the names the DRF edge seeds for
the views' own dispatch. DRF records the view on the Request it builds
(parser_context["view"]); a plain HttpRequest carries none, and then
view is left to the caller rather than seeded as None. user is
None on a request that bypassed authentication middleware.
resolve_progress_hook ¶
First progress reporter the view offers: per-action hook, then catch-all.
Most-specific wins and there is no merging — a reporter is a single sink,
not a set of keys, so layering two of them would mean silently fanning out;
a view that wants that composes them itself with
combine_progress. None when the view
offers neither, which leaves the seed as
null_progress.
A hook naming call_context is handed the request's dict
(view_call_context), and one taking **kwargs alone is not.
resolve_view_hooks ¶
resolve_view_hooks(
view: Any, request: Request, *, chain: str = "service", instance: Any = None
) -> ViewHooks
Resolve the calling view's hook chains into a
ViewHooks carrier.
View layers only — every spec_* argument below is deliberately None. The
chains run view.get_<x> → view.get_<action>_<x> → spec.<x>, and
dispatch_spec owns that last layer; resolving the spec provider here too would
invoke it twice, which is not safe for a provider that queries the database. See
ViewHooks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chain
|
str
|
Which kwargs chain to collect — |
'service'
|
instance
|
Any
|
The resolved mutation target ( |
None
|
add_affordances ¶
add_affordances(
spec: ServiceSpec[Any, Any, Any] | SelectorSpec[Any, Any],
data: Any,
value: Any,
*,
many: bool,
) -> Any
data with the rendering spec's affordance answers, when it declares any.
value is what the serializer rendered -- the row, the page, or the
queryset it iterated, whose result cache the second walk reuses. Nothing is
added to a None value, which renders as nothing to carry answers.
list_with_affordances ¶
DRF's ListModelMixin.list, with each rendered row carrying its answers.
The same filter, paginate and serialize steps in the same order; the answers are added to the serialized rows before a paginator wraps them, so every paginator's envelope holds the same objects the unpaginated response does.
Spec validation¶
spec_validation ¶
Fail-fast validation of service / selector signatures at view setup time.
resolve_callable_kwargs forwards pool∩signature, so a required parameter
the framework cannot supply is omitted rather than rejected and the call dies
as a bare TypeError deep in dispatch, at the first request. These helpers
surface the same misconfiguration at as_view() time with a precise message.
Deliberately lenient on extras: a kwargs provider or an overridden
get_*_kwargs may be feeding the callable keys the validator cannot see.
validate_callable_signature ¶
validate_callable_signature(
fn: Callable[..., Any],
*,
spec_label: str,
has_data: bool,
has_instance: bool,
has_result: bool,
spec_kwargs: Callable[..., Any] | None,
permissive_extras: bool,
extra_known_keys: Iterable[str] = (),
) -> None
Raise ImproperlyConfigured on a misconfigured service / selector.
has_data / has_instance / has_result say whether those
framework-injected keys exist at this call site; requiring one that does
not always fails, since no user override can supply it. Every other required
parameter is checked only when nothing could be feeding it
(permissive_extras false and no spec_kwargs). extra_known_keys
extends the allowed set for call sites that seed additional names.
is_overridden ¶
Return True if view_cls overrides base_cls's method_name.
Drives permissive_extras: an overridden get_*_kwargs is assumed to
contribute keys the validator cannot see.
validate_filter_set_no_backend_conflict ¶
validate_filter_set_no_backend_conflict(
view_cls: type, spec: SelectorSpec[Any, Any], *, label: str
) -> None
Reject a list selector that sets filter_set and wires DjangoFilterBackend.
On the list path DRF's list() runs filter_queryset() while the
dispatcher also applies spec.filter_set — equivalent operations, so the
queryset would be filtered twice. Callers must gate this on the list path
only: the selector retrieve path overrides get_object() and never calls
filter_queryset, so there is no conflict there.
validate_service_spec ¶
validate_service_spec(
spec: ServiceSpec[Any, Any, Any],
*,
label: str,
has_instance: bool,
permissive_extras: bool,
) -> None
Validate a
ServiceSpec's
service and nested selector specs.
Shared between standalone mutation views, viewset mixins, and
@service_action. has_instance is fixed by the action context
(False for create, True for update / destroy / detail actions).
validate_polymorphic_service_spec ¶
validate_polymorphic_service_spec(
poly: PolymorphicServiceSpec,
*,
label: str,
has_instance: bool,
permissive_extras: bool,
) -> None
Validate every variant of a
PolymorphicServiceSpec
+ its strategy.
Each variant is validated with the same has_instance /
permissive_extras as a plain entry would be.
validate_selector_spec ¶
validate_selector_spec(
spec: SelectorSpec[Any, Any],
*,
label: str,
expected_kind: SelectorKind | None = None,
) -> None
Validate a
SelectorSpec's
selector.
Selectors are always permissive on extras (URL kwargs and get_selector_kwargs
are dynamic), so the only fatal misuses are framework-only keys absent from the
selector pool. expected_kind catches a spec mounted on the wrong view — a
LIST spec on
SelectorRetrieveView
would otherwise misbehave only at runtime.
validate_mutation_view_spec ¶
Validate view_cls.spec on a standalone mutation view.
No-op when spec is unset — the base classes inherit a None
placeholder so as_view() itself doesn't trip.
validate_selector_view_spec ¶
Validate view_cls.spec on a standalone selector view.
No-op when spec is unset — that means "use vanilla DRF". expected_kind is
the kind the view is shaped for (LIST for
SelectorListView,
RETRIEVE for
SelectorRetrieveView).