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 ¶
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, receiving only the subset of
{view, request} it declares (or the whole pool via **kwargs).
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
|
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.
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.
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
|
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).