Skip to content

Views

Mutation views

ServiceCreateView

Bases: MutationFlowMixin, GenericAPIView

POST endpoint that runs a service callable to create a resource.

Configure by setting spec to a :class: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 :class: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 :class: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 :class:SelectorSpec to configure the selector and/or the output serializer. Both fields are optional:

  • spec.selector overrides get_queryset(); None falls back to the inherited queryset attribute.
  • spec.output_serializer overrides get_serializer_class(); None falls back to DRF's standard serializer_class attribute.

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

get_selector_kwargs() -> dict[str, Any]

Hook for additional kwargs available to the selector signature.

SelectorRetrieveView

Bases: RetrieveModelMixin, GenericAPIView

GET endpoint that returns a single object.

Set spec to a :class:SelectorSpec to configure the selector and/or the output serializer. Both fields are optional:

  • spec.selector overrides get_object(); None falls back to self.get_object() — standard DRF lookup using queryset and lookup_field. Returning None or raising Model.DoesNotExist results in a 404 — or, when the spec sets allow_none=True, a 200 with a JSON null body (the nullable-resource contract; the output serializer is skipped).
  • spec.output_serializer overrides get_serializer_class(); None falls back to DRF's standard serializer_class attribute.

spec = None (the default) keeps both as vanilla DRF.

Mutation flow mixin

MutationFlowMixin

Provides _run_mutation for service-backed views and viewset mixins.

The actual flow lives in :func:dispatch_mutation_for_spec (so @service_action can reach it without being a class). This mixin is the OO entry point: the per-action mixins (ServiceCreateMixin etc.) and the standalone single-purpose views compose it and call self._run_mutation(...) after resolving their per-action spec.

Three layers contribute extra kwargs to every service call (most specific wins):

  1. get_service_kwargs(self) — global fallback on the view.
  2. get_<action>_service_kwargs(self) — per-action override (viewsets only; self.action must be set).
  3. ServiceSpec.kwargs — per-spec callable, co-located with the service it feeds.

A symmetrical three-layer chain feeds the serializer's input dict (merged on top of request.data before validation):

  1. get_input_data(self, request) — global fallback on the view.
  2. get_<action>_input_data(self, request) — per-action override.
  3. ServiceSpec.input_data — per-spec callable.

Two further three-layer chains feed the context= argument passed to the input serializer (during validation) and the output serializer (during response rendering):

  1. get_serializer_context(self) — DRF's default (request, view, format).
  2. get_input_serializer_context(self) / get_output_serializer_context(self) — directional fallback; defaults to self.get_serializer_context() so a single get_serializer_context override flows into both directions.
  3. get_<action>_input_serializer_context(self) / get_<action>_output_serializer_context(self) — per-action override (viewsets only; self.action must be set).

Each layer's result is merged with dict.update so the more specific hook wins on overlapping keys.

get_service_kwargs

get_service_kwargs() -> dict[str, Any]

Hook for additional kwargs available to every mutation service.

get_input_data

get_input_data(request: Request) -> Mapping[str, Any]

Hook for extras merged on top of request.data before validation.

get_input_serializer_context

get_input_serializer_context() -> dict[str, Any]

Hook for the context= dict passed to the input serializer.

Defaults to :meth:get_serializer_context so overriding the DRF-standard hook flows into the input-validation path automatically. Override here to inject keys visible only during input validation.

get_output_serializer_context

get_output_serializer_context() -> dict[str, Any]

Hook for the context= dict passed to the output serializer.

Defaults to :meth:get_serializer_context so overriding the DRF-standard hook flows into the response-rendering path automatically. Override here to inject keys visible only during response rendering.

get_permissions

get_permissions() -> list[Any]

Honor spec.permission_classes on standalone mutation views.

Standalone Service*View subclasses carry spec as a class attribute. When the spec sets permission_classes it wins over the view's class-level permission_classes; None (the default) 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 :class: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 DRF :class:~rest_framework.request.Request.
  • 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; None on 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.

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, applied in order so that the more specific override the more general:

  1. view.<catch_all_hook>() — global fallback declared on the view (get_service_kwargs / get_selector_kwargs). No-op when the method is not present.
  2. view.<action_hook>() — per-action method on the view, e.g. get_create_service_kwargs / get_list_selector_kwargs. Skipped when action_hook is None (e.g. on standalone single-purpose views) or the method is absent.
  3. spec_kwargs(view, request) — per-spec callable from :attr:ServiceSpec.kwargs / :attr:SelectorSpec.kwargs.

Each layer's result is merged with dict.update, so the spec-level provider has the final say on any overlapping keys.

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.

Mirrors :func:resolve_extra_kwargs but for the input_serializer-bound data, not the service-call pool. Layers, applied in order of increasing specificity (later wins on overlap):

  1. view.<catch_all_hook>(request) — global fallback (get_input_data); typically returns {}.
  2. view.<action_hook>(request) — per-action method on the view (get_<action>_input_data). Skipped when action_hook is None (standalone single-purpose views) or the method is absent.
  3. spec_input_data(view, request) — per-spec callable from :attr:ServiceSpec.input_data.

Each layer's result is merged with dict.update so the spec-level provider has the final say on overlapping keys.

extras carries the resolved data available before validation — currently the mutation target instance (None on create). Each provider receives only the extras it declares by name (see :func:_invoke_with_extras); legacy providers are unaffected.

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.

Same precedence rules as :func:resolve_serializer_context, but takes the layer-1 dict explicitly instead of calling view.get_serializer_context(). Used by get_serializer_context() overrides that need to extend super().get_serializer_context() without recursing.

direction_hook=None skips the directional layer entirely. The canonical use of this is _ActionSpecsMixin.get_serializer_context, which can't safely call get_output_serializer_context because the default implementation on :class:MutationFlowMixin would recurse back into get_serializer_context.

extras carries the resolved data about to be serialized (the result of a mutation, the retrieved instance, or the list page). Each provider receives only the extras it declares by name (see :func:_invoke_with_extras); legacy (view, request) providers are unaffected. None is treated as an empty mapping.

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, applied in order so that the more specific override the more general:

  1. view.get_serializer_context() — DRF's default, available on every :class:~rest_framework.generics.GenericAPIView (returns request, view, format).
  2. view.<direction_hook>() — library directional fallback (get_input_serializer_context / get_output_serializer_context). Skipped when the method is absent, so plain DRF viewsets work unchanged.
  3. view.<action_hook>() — per-action override on the view, e.g. get_create_input_serializer_context / get_list_output_serializer_context. Skipped when action_hook is None (standalone single-purpose views) or the method is absent.
  4. spec_provider(view, request) — per-spec callable from :attr:ServiceSpec.input_serializer_context / :attr:ServiceSpec.output_serializer_context / :attr:SelectorSpec.output_serializer_context. Skipped when None.

Each layer's result is merged with dict.update, so the spec-level provider has the final say on overlapping keys.

extras (the resolved data — result / instance / page) is offered to the directional, action, and spec providers by keyword, each receiving only the names it declares. See :func:layer_serializer_context.

get_class_attr

get_class_attr(view: Any, name: str) -> Any

Return the named class attribute without instance binding.

Functions stored as plain class attributes (e.g. service = my_fn) would otherwise be wrapped in a bound method when accessed via self. Use this helper to retrieve them as the original callable.

resolve_callable_kwargs

resolve_callable_kwargs(fn: Callable[..., Any], pool: dict[str, Any]) -> dict[str, Any]

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.

Spec validation

spec_validation

Fail-fast validation of service / selector signatures at view setup time.

The framework already filters its kwargs pool through :func:resolve_callable_kwargs at request time, which means a service that declares a required kw-only parameter the framework cannot provide fails late, deep in the dispatch path with a generic TypeError: missing required keyword-only argument message. The helpers here surface those errors at as_view() time with a precise diagnostic so misconfigurations turn up at module import / URL wiring instead of at the first request.

The validator is intentionally lenient on extras: when a callable could be fed by ServiceSpec.kwargs / SelectorSpec.kwargs or by an overridden get_*_kwargs method, the validator assumes those overrides supply whatever the signature needs and only fails on the unambiguous misuses.

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 :exc:ImproperlyConfigured on a misconfigured service / selector.

has_data / has_instance / has_result describe whether those framework-injected keys will be present for this call site. Mismatches on those (e.g. a service requiring data when input_serializer is unset) always fail — they cannot be papered over by the user.

For other required kw-only parameters the validator is lenient: if permissive_extras is True (because the view overrides get_*_kwargs or spec_kwargs is supplied) it assumes those overrides contribute the missing keys and skips the check. Otherwise it raises with a clear hint.

extra_known_keys lets callers extend the always-allowed set (e.g. selectors include the URL kwargs they expect to see).

is_overridden

is_overridden(view_cls: type, base_cls: type, method_name: str) -> bool

Return True if view_cls overrides base_cls's method_name.

Used to decide whether the framework should assume an get_*_kwargs override is contributing extras (and therefore relax signature validation for that callable).

validate_service_spec

validate_service_spec(
    spec: ServiceSpec[Any, Any, Any],
    *,
    label: str,
    has_instance: bool,
    permissive_extras: bool,
) -> None

Validate a :class: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_selector_spec

validate_selector_spec(
    spec: SelectorSpec[Any, Any],
    *,
    label: str,
    expected_kind: SelectorKind | None = None,
) -> None

Validate a :class:SelectorSpec's selector.

Selectors are always permissive on extras (URL kwargs and get_selector_kwargs are dynamic), so the only fatal misuses are requesting framework-only keys that don't exist in the selector pool (data, instance, result).

expected_kind (when supplied) fails fast if spec.kind does not match — e.g. a LIST spec mounted on :class:SelectorRetrieveView raises at as_view() time rather than producing surprising runtime behaviour.

validate_mutation_view_spec

validate_mutation_view_spec(view_cls: type, *, has_instance: bool) -> None

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_selector_view_spec(view_cls: type, *, expected_kind: SelectorKind) -> None

Validate view_cls.spec on a standalone selector view.

No-op when spec is unset (the spec then means "use vanilla DRF" and there is nothing to validate). expected_kind is the kind the view is shaped for (LIST for :class:SelectorListView, RETRIEVE for :class:SelectorRetrieveView); a spec whose kind does not match fails fast at as_view() time.