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 ¶
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 ¶
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 ¶
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.kwargs →
get_<action>_selector_kwargs → get_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.