Skip to content

Selectors

Protocols

Each Protocol is parameterised on input / instance / result types only; **extras is typed Any. Strict-typed extras live on the user's function signature via **extras: Unpack[YourKw] — see Typing services and selectors for the full pattern.

Selector

Bases: Protocol

Any callable returning a queryset, list, or single object.

Used as a structural type for documentation purposes; the views accept any plain callable, so satisfying this protocol is optional.

AsyncSelector

Bases: Protocol

Async sibling of :class:Selector.

ListSelector

Bases: Protocol[ResultT]

Structural shape for a list-action selector callable.

The framework calls this from get_queryset(); the returned iterable flows through DRF's filter backends, pagination, and output_serializer.

See :class:~rest_framework_services.services.CreateService for the extras-typing notes.

RetrieveSelector

Bases: Protocol[ResultT]

Structural shape for a retrieve-action selector callable.

The framework calls this from get_object(). Returning None (or raising Model.DoesNotExist) results in a 404. The URL lookup field (typically pk) is delivered via **extras.

See :class:~rest_framework_services.services.CreateService for the extras-typing notes.

Helpers

call_selector

call_selector

call_selector(
    selector: Callable[..., ResultT], *, request: Request, **extras: Any
) -> ResultT

Invoke selector with the framework's kwargs pool.

request is required; user is derived from request.user. Async selectors are bridged via async_to_sync.

acall_selector

acall_selector async

acall_selector(
    selector: Callable[..., ResultT] | Callable[..., Awaitable[ResultT]],
    *,
    request: Request,
    **extras: Any,
) -> ResultT

Invoke selector from async code with the framework's kwargs pool.

Async selectors are awaited; sync selectors are called inline.

Dispatch

utils

Internal selector dispatch helpers (sync + async).

is_queryset

is_queryset(obj: Any) -> bool

True for Django QuerySet objects and Manager instances.

These are the queryset-shaping targets: the things the four shaping fields can be applied to, and the things a RETRIEVE selector / output selector should be materialized from via .first(). Centralizes the "is this a queryset?" decision so the selector and mutation dispatch paths agree on one definition instead of duck-typing on a method name (hasattr(..., "first")), which would also match an unrelated domain object that happens to expose first. QuerySet subclasses (.values(), .values_list(), polymorphic querysets, …) all pass.

run_selector

run_selector(fn: Callable[..., Any], kwargs: dict[str, Any]) -> Any

Call a selector from sync code, transparently bridging async ones.

arun_selector async

arun_selector(
    fn: Callable[..., Any] | Callable[..., Awaitable[Any]], kwargs: dict[str, Any]
) -> Any

Call a selector from async code; sync ones run inline.

apply_queryset_shaping

apply_queryset_shaping(
    qs: Any,
    view: Any,
    request: Request,
    *,
    select_related: Any,
    prefetch_related: Any,
    annotations: Any,
    extend_queryset: Any,
    source_label: str,
) -> Any

Apply the four shaping fields to qs.

Declarative fields apply first (in declaration order), then extend_queryset runs so the user callable always sees the fully statically-shaped queryset. Returns qs unchanged when no shaping is configured.

Raises :exc:ImproperlyConfigured when shaping is configured but qs is not a Django QuerySet (no annotate method) — loud failure beats a stale AttributeError deep in DRF rendering. source_label is included in the error to point at the misuse ("SelectorSpec.selector" vs "ServiceSpec.output_selector_spec.selector").

dispatch_selector_for_spec

dispatch_selector_for_spec(
    view: Any,
    spec: SelectorSpec[Any, Any],
    *,
    extra_url_kwargs: dict[str, Any] | None = None,
    source_label: str = "SelectorSpec.selector",
) -> Any

End-to-end dispatch for one SelectorSpec call.

Runs the kwargs-resolution chain (spec.kwargsget_<action>_selector_kwargsget_selector_kwargs), filters the resulting pool against the selector's signature, invokes the selector sync-or-async, then applies declarative + dynamic queryset shaping. Used by both selector viewset mixins and the standalone selector views so the call shape lives in one place.

When spec.kind is :attr:SelectorKind.RETRIEVE, the returned QuerySet (if any) is materialized via .first() and None / :exc:~django.core.exceptions.ObjectDoesNotExist are translated to :exc:~rest_framework.exceptions.NotFound — unless the spec sets allow_none=True, in which case the missing-object case returns None and the calling view decides how to render it (the retrieve views render 200 + JSON null). SelectorKind.LIST returns the (optionally shaped) selector return as-is.

The caller must check spec.selector is not None before calling and fall back to vanilla DRF otherwise.

source_label is forwarded to :func:apply_queryset_shaping for error messages, so a misconfiguration on a nested output_selector_spec points at the right place.