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.
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 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 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.
The one definition of a queryset-shaping target: what the shaping fields may
be applied to, and what a RETRIEVE selector materializes from. Tested by type
rather than by hasattr(…, "first"), which would also match a domain
object that happens to expose first. QuerySet subclasses 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,
filter_set: Any = None,
filter_data: Any = None,
source_label: str,
) -> Any
Apply the five shaping fields to qs.
The order is fixed: the declarative fields first (in declaration order),
then extend_queryset, so the user callable always sees the fully
statically-shaped queryset, and finally filter_set, so filtering
composes with shaping and runs before the retrieve .first()
materialization the caller does next.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
Passed to |
required |
filter_set
|
Any
|
Applied by duck typing as
|
None
|
filter_data
|
Any
|
The flat |
None
|
source_label
|
str
|
Named in the misconfiguration error to point at the
offending callable ( |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The shaped queryset, or |
Raises:
| Type | Description |
|---|---|
ImproperlyConfigured
|
Shaping is configured but |
ValidationError
|
|
materialize_retrieve ¶
Collapse a RETRIEVE selector's return to the single instance, or None.
The one definition of what kind=RETRIEVE means once the selector has run:
a queryset materializes through .first() — so an author can write
selector=lambda *, pk: Model.objects.filter(pk=pk) and still get the
spec's shaping applied first — and anything else passes through as the
resolved object. What each caller does with a None differs; how the value
is arrived at does not.
amaterialize_retrieve
async
¶
Async twin of materialize_retrieve, awaiting .afirst().
Separate because the materialization is the query — .first() would
block the event loop. Same rule otherwise; keep the two in step.
check_view_object_permissions ¶
TargetGuard running
DRF's object-permission check for an HTTP view.
The HTTP counterpart of
enforce_permissions:
off HTTP a transport enforces spec.permission_classes itself, while a DRF view
has already instantiated them and exposes check_object_permissions. Both plug
into the same on_target_resolved seam, so the core stays authz-agnostic on every
transport.
Gated on Model, like enforce_permissions: the core fires this hook on
the LIST branch too, with the resolved queryset, and object permissions
are per-row. None — a create, or a bulk list payload — is skipped too.
dispatch_selector_for_spec ¶
End-to-end dispatch for one SelectorSpec call from a DRF view.
Resolves the view's get_selector_kwargs / get_<action>_selector_kwargs chain
into a ViewHooks carrier,
hands off to the one
dispatch_spec
core, and translates the neutral
DispatchResult
into the view-layer contract: a RETRIEVE that resolved nothing raises NotFound,
unless the spec sets allow_none=True (then None, and the retrieve views
render 200 + JSON null). SelectorKind.LIST returns the shaped queryset.
argument_binding=BUNDLE is what keeps HTTP semantics. Off HTTP the
flat params mapping is the argument channel; over HTTP a selector's
kwargs come from route captures plus the hook chain, and the query string
belongs to filter_set / the filter backends. BUNDLE spreads nothing,
so passing query_params feeds the filter without widening the argument
channel.
There is deliberately no extra_url_kwargs parameter: the core reads
view.kwargs itself via view_url_kwargs, which strips the reserved
pool seeds, so a nested route like /users/<user>/posts/ cannot let the
captured value shadow the authenticated user. Taking the mapping from a
caller would reopen that.
The caller must check spec.selector is not None first and fall back to
vanilla DRF otherwise.