Service specs on the kernel¶
A ServiceSpec or SelectorSpec declared here is a source for
django-service-specs: it answers
as_kernel_spec(), so the kernel's kernel_spec(spec) compiles it once, and
dispatch, authorize and a SpecRegistry take the drfs spec itself. Every
drfs field lands on a kernel seat or is named below as the HTTP views' own.
A many spec's list is judged differently by the two routes.
dispatch_spec judges the list on the rows sent: where the list
as sent is within its length bounds, a row that is no object is refused alone,
and the list's validate() is left to the rows as sent. The kernel's dispatch
called directly on a drfs spec has no such body to hold the rows and judges the
list on the rows that survived, in the kernel's own classes and words:
[good, 1] under a minimum of two answers row 1's Expected an object. beside
Ensure this field has at least 2 elements., and a list-level validate() runs
over the rows left.
What a spec declares for a caller off HTTP¶
Over HTTP a URLconf captures a nested route's project_pk and a query string
carries fields. Off HTTP somebody has to say they exist, so a spec declares
them itself:
from rest_framework_services import QueryParam, SelectorKind, SelectorSpec, UrlKwarg
list_tasks = SelectorSpec(
kind=SelectorKind.LIST,
selector=tasks_for_project,
reads=[
UrlKwarg("project_pk", type="integer", required=True),
QueryParam("fields", description="Fields to return."),
],
)
reads: eachUrlKwargandQueryParambecomes one kernelParameter, so it is an argument a caller sends and the schema lists. AQueryParamno callable of the call takes by name compiles to the kernel spec'sshapesrather than itsreads, and reaches the presenter onPresentationContext.shapes(seeQueryParam). An entry of the same name as a selector parameter replaces the reflected one and keeps what that parameter requires: a read naming a parameter with no default, of the selector, a target lookup, the service or a precondition, is required too, unless the read declares adefault; the compiled parameters and the kernel's input schema say so, and a parameter markedInputRequiredstays required under the read. A sequence is required, and a name may be declared once. A selector taking a bare**kwargsis not handed a route capture no callable declares: declare it here or as a keyword parameter. AUrlKwarg'stypehas to be the one the route's converter yields:UrlKwarg("pk", type="integer")for<int:pk>/, since the defaultstringrefuses anintcapture asExpected a string..field_audiences: per-field markings layered over the output serializer's own. On aServiceSpecthey layer over its output selector's. A spec declaring them with no output serializer is refused at compile, with a message naming both.agent_contract(): theOfflineContractthese two declare, which a registry entry answers for transports that read it.Nonewhen both are empty.
Where each field lands¶
| drfs field | Kernel seat |
|---|---|
permission_classes |
one DRFPermissionCheck; [] is Unrestricted(), and None is no permissions, which a registry refuses |
input_serializer |
validator (a SerializerValidator) |
output_selector_spec.output_serializer, output_serializer |
presenter (a SerializerPresenter) |
input_serializer_context |
the SerializerValidator's serializer_context |
output_selector_spec.output_serializer_context, output_serializer_context |
the SerializerPresenter's serializer_context |
partial |
partial, kept only with an input_serializer |
many, many_argument |
the same; the list bounds come from the serializer's ListSerializer and apply off HTTP and on an OfflineServiceView, while a live view's bulk body is bounded by the ListSerializer alone |
extend_queryset |
extend_queryset, called as extend(queryset, view, request) |
affordances |
each service's compiled spec |
| everything else of the same name | carried by name |
A service with no input_serializer declares none of its own parameters, so
kernel_spec(spec).parameters() lists only its target lookup's keys and its
reads, and no body reaches the service's keywords. Its signature is declared
only in the variant compiled for an explicit
ArgumentBinding.SPREAD_AUTHOR_WINS (kernel_spec_variant(spec, spread=True)).
A selector always takes its arguments from its signature, except for the names
its filter_set owns.
Parameters marked NotClientInput on any callable of the call are hidden.
So are the names the pool fills: user, request, view, queryset,
result and the rest.
parameters() is not available on every compiled spec. Where a target lookup's
selector reads a name that is also a writable input field, such as renaming a
row by the slug that finds it, kernel_spec(spec).parameters() and
as_kernel_spec(spec).parameters() raise ImproperlyConfigured ("Parameters
['slug'] are declared by two sources"), because the kernel declares one source
per name. Such a spec still registers and dispatches, and spec_to_json_schema
describes it: the route's capture scopes the lookup, and the body's value is
the field's.
Three reads declarations are refused with ImproperlyConfigured when the
spec compiles (at registration, or whenever it is dispatched, over HTTP too),
each naming the read:
- a name the pool fills, which would never be the caller's;
- a name the spec's service or selector, or one of its
preconditions, marksNotClientInput. Dispatch hands a declared read to each of those callables by name, so the same callable would be given the client's value for a parameter it keeps the server's. The message names the callable and the mark; remove one of the two; - a name a writable field of the input serializer also takes, declared on the
spec or on its target lookup. The body's value of that name is the field's,
so the read would reach neither the service nor the lookup. The message
names the read, the serializer and, where it reads the name too, the target
lookup. A
UrlKwargthe target lookup reads is accepted: that is the rename-by-key shape above, where the route's capture scopes the lookup. Amanyspec's fields are each row's, and a read-only field and aHiddenFieldtake nothing from the body, so none is a clash: aUrlKwargnamed like aHiddenFieldwhose default readsview.kwargsis how a route capture scopes a write, and off HTTP the caller's value for it is written ontoview.kwargs, where the default reads it.
A serializer whose fields cannot be read when the spec compiles - they read
the request, or query a table migrate has not made yet, or a row not yet
there - is taken to declare no name, so registering it at import never
needs the database; the call that describes it is where such a read is
refused. A many=True spec whose serializer cannot be built then compiles
with no list bound of its own, and the list serializer that validates the
call still holds the list to the bound it declares.
A name only a target lookup marks NotClientInput is accepted: the lookup is
never handed a read it hides, so the read is the service's argument and the
lookup keeps its own value.
A nested spec compiles to what the kernel runs:
- its
permission_classesare not carried; - an instance lookup's
allow_noneandoutput_serializerare dropped; - an output selector's
kwargsare dropped; - an output selector with no
selectormoves its serializer onto the service spec'spresenter; preconditionsandprogress_reporter, which would never run there, are refused.
success_status, document_service_error and response_finalizer are the
HTTP views' and are not compiled.
The two serializer context callables ride on the adapters and are asked when
the serializer is built for a call, over DRF's own context and the view hooks
dispatch_spec registers for the call; see
Customise serializer context.
The serializer as Validator and Presenter¶
SerializerValidator is the kernel's Validator over an input_serializer
(a DRF serializer or a dataclass): its parameters are the serializer's fields,
and it validates a call with the target as instance, the call's partial,
and a serializer context built for the principal, so CurrentUserDefault
reads it. A HiddenField or a read-only field is not a parameter, and neither
is a field named like a seed (user, result, a registered seed's name): the
serializer reads it from the body, so its value lands in data and the seed
keeps its own. For a request a live DRF view dispatches, the edge compiles a
lenient variant (kernel_spec_variant(spec, lenient=True)) whose parameters
carry their names alone, so DRF's serializer is the only validator and answers
in its own words; off HTTP the parameters carry their types and the kernel
checks them first.
A required field named like a seed has no parameter, so no call through the
kernel's dispatch can send it, although HTTP and dispatch_spec read it from
the body. SerializerValidator.unsendable names those fields, a frozenset of
field names computed beside parameters(), so a transport can refuse such a
spec before a call reaches it: getattr(compiled.validator, "unsendable",
frozenset()). It names a required user, result or serializer, a many spec's
row fields included, and the names the edge's variant excludes. It names no optional, defaulted, hidden or
read-only field, no field the Validator declares a parameter for (a field named like a
registered seed is one on the compiled spec), and says nothing of partial,
which is the compiled spec's. Nothing in drfs reads it; HTTP is unchanged.
SerializerPresenter is the kernel's Presenter over an output serializer,
with the spec's field_audiences layered over the serializer's own markings.
SerializerValidator ¶
Bases: Validator
A Validator whose declaration is a DRF serializer, or a dataclass read through one.
It takes what a drfs spec's input_serializer takes: a Serializer
subclass, ModelSerializer and DataclassSerializer included, or a
dataclass, which is validated through a DataclassSerializer exactly as
drfs' dispatch validates it. Anything else is refused with
ImproperlyConfigured when the Validator is built.
parameters() walks the serializer's fields, in order, as the field walk
in service_specs/utils.py reads them: a nested serializer is an object
with Parameters of its own, many=True and a ListField an array, a
file field a file, choices with their labels; a HiddenField and a
read-only field are no parameter. It is read on each call rather than
when the Validator is built, because specs are built at import time, when
a ModelSerializer cannot yet read its model, and so that help is
rendered in the language active when it is asked for. It never queries.
validate() builds the serializer as a DRF view would:
-
over the body the call was sent, where a DRF edge call names this Validator (
validator_input): the caller's own data, aQueryDictkept one, with what the edge put over it, as a DRF view's serializer was always handedrequest.data. Anywhere else, the kernel's checked arguments. -
with the target as
instance, so an update's uniqueness checks exclude the row being updated. The queryset a collection selector resolves is no row, so a call over one binds as a create, as drfs' dispatch binds it; the target is handed over as it is, since a serializer, unlike aModelForm, writes nothing onto its instance while it validates. - partial as the call is, the context's
partial, so a partial call may leave out a required field and returns only what was sent. - under the serializer context DRF builds on HTTP,
request,formatandview. Therequestandviewthe call registered as pool seeds are the serializer's own, read fromValidationContext.ambientas the kernel resolved them, and where a seed is absent orNone, the entry of that name inValidationContext.call_context. A call that carries neither gets an offline request whoseuseris the principal and whosedatais the arguments, built bybuild_offline_context, so aCurrentUserDefaultreads the principal and a field or avalidate()readingself.context["request"]finds a request rather than aKeyError. What a registered request'suseris, is the transport's to make the principal; the Validator never writes to a call's own objects. Over that, the input half of the view hooks a DRF edge carries for the call (input_view_layers), thenserializer_context, the spec's owninput_serializer_context, each winning on a key it shares with what it layers over (seeserializer_contextinservice_specs/utils.py). The callable declares what it reads from the call's pool:request,view,user,call_context, a registered seed, or an argument. It is asked once per call, amanycall's included, except where the call builds the serializer twice, for a body the server puts a value into or a row a target lookup resolves, and what the callable is handed differs between the two.
A refusal is one
InvalidArguments,
the kernel's refusal that is DRF's ValidationError as well, carrying
the serializer's errors unchanged: DRF's ErrorDetail leaves, each
with its code, cross-field messages under non_field_errors, and a
nested list's errors in whichever shape the installed DRF writes them.
validated() hands the service what drfs' dispatch hands it: data
is the serializer's validated_data - the validated mapping, or the
dataclass instance a DataclassSerializer builds - and the bound
serializer is bound for the call as serializer. values follow
what validated_data is, as drfs' dispatch reads it: a mapping's
entries, keyed as DRF keys them, by each field's source; a dataclass
instance's __init__ fields, whether a dataclass was declared or a
DataclassSerializer subclass was; and nothing for any other object a
serializer's validate() returns, which reaches the service only as
data. A key named like a seed of the call is a value only in
data: DRF's HiddenField(default=CurrentUserDefault()) named
user, or a key a validate() adds named result, stays in
validated_data as the serializer built it, and the service's user
is the call's principal, as drfs always handed them (see _unseeded).
So is a key named in server_owned, the names a callable of the call
marks NotClientInput, which the compiled spec hands it: the client's
value is the service's in data alone, unless the edge put the server's
value into the field it comes from (see _unowned). Where the edge did,
that field's validation of the value the edge put is the key's value,
validated on its own once the serializer has accepted the body, never read
back off the body (see _server_values), whatever the field reads from
the body, and whatever a later field of the same source, a
source="*" nested serializer, a validate() or a
to_internal_value override did to the key: it is the parameter's, by
the field's source or, where that is no single key, by the field's name,
and by the key an author named it under (see _handed), and it is laid
back over the key in a
mapping data (see _lay_back). A dataclass instance is data as
built, and a many call's rows are neither validated apart nor laid back.
A partial call over a dataclass is refused, a DataclassSerializer
subclass included: one builds its instance from every field, and a field
a partial call left out fails as a TypeError from inside the library.
supports_partial is therefore answered per instance, False for
either declaration and True for any other serializer, so a spec
declaring partial=True over a dataclass is refused when it is built.
A partial call made on the Validator directly is refused with
ImproperlyConfigured as well.
validated_many() validates a many spec's rows as DRF's own list
serializer does, once: the serializer's child class under
many=True (so a Meta.list_serializer_class and its list-level
validate() run), with the call's context and partial flag. The one
list serializer is bound for the call as serializer, data is the
list of each row's validated_data and the values are the rows' values
under many_argument. A refusal is an InvalidArguments whose detail
keys each failing row by the index the caller's list gave it, whichever of
DRF's two shapes the installed version reports item errors in, and whose
list-level message sits under non_field_errors. A live view's list
body (EdgeCallState.body_rows) is bound whole, every row as sent, and
what the list serializer refused is also left on the edge's state as DRF
reported it, for the edge to answer with as a view always did. Where the
kernel refused a row the edge sent, the rows it passed are read but the
list is judged as sent: its length bounds count every row sent, and its
list-level validate() does not run, as DRF never runs it past a
refused row.
unsendable
property
¶
The required input fields no call on the kernel route can send.
A field named like a seed (RESERVED_POOL_SEEDS, BOUND or
excluded) is no parameter (see parameters()), so the kernel has
nowhere to take its value from, while HTTP and dispatch_spec read it
from the body. Where the serializer requires such a field, a call
through the kernel's dispatch is refused for it however it is made.
This names those fields, by the name parameters() would have
declared them under, so a transport can refuse such a spec before a
call reaches it::
getattr(compiled.validator, "unsendable", frozenset())
- Only what
parameters()omits. A field the Validator declares a parameter for is never named, even one a seed, a server argument or a call seed then fills: a field named like a registered seed is one, since the compiled spec leavesexcludedempty and only the DRF edge's variant fills it. A required field calledserializeroruseris named. - Required only. An optional field, one with a default, a
read_onlyfield and aHiddenFieldare no input to require (test_an_optional_field_is_not_unsendable, test_a_read_only_field_is_not_unsendable, test_a_hidden_field_is_not_unsendable), and amanyspec's row fields are read through the row serializer this Validator holds, so a row's requireduseris named and a row'stenantis not (test_a_many_specs_row_fields_are_read_through_the_row_serializer). - Not partial. Whether a call may leave a required field out is
the compiled spec's
partial, not this attribute's question; it names the field whether or not the spec is partial (test_a_partial_spec_names_the_same_fields). - Not
lenient. The declaration relaxes what each parameter accepts and not which names are parameters (test_a_lenient_validator_names_the_same_fields).
Computed on each read, from the serializer parameters() describes,
for the reason parameters() is: a ModelSerializer cannot read
its model when specs are built. HTTP is unchanged: nothing in drfs reads it.
parameters ¶
The serializer's input fields, as Parameters.
Read off describe_serializer: inside an edge call, the serializer
the call builds once and validates with, so a serializer that queries
as it is built queries once in the call
(test_its_queries_run_once_off_http); outside one, a serializer built
afresh for each read, as a schema or a registration reads it.
A field named like a seed of the call is no parameter: one of the
dispatcher's own names (RESERVED_POOL_SEEDS,
test_a_writable_field_named_user_validates_off_http) and the
serializer this Validator binds (BOUND,
test_an_input_field_named_serializer_validates_into_data) always, and each of
excluded, which the DRF edge compiles with the names the call's
registered seeds take
(test_a_writable_field_named_like_a_registered_seed_validates). The
kernel refuses a parameter a seed occupies, where a serializer was
always free to declare such a field: the client sent its value, the
serializer validated it into data, and the seed kept its own. So
the field is the serializer's alone, read from the body the edge hands
it (validator_input), and never meets the seed in the call's pool.
lenient declares each field by name alone - any value, optional,
nullable - so the kernel's shape check passes every value through and
the serializer is the one to answer it, as a live DRF view's request
always was answered (kernel_spec_variant(lenient=True)).
validate ¶
The validated values, keyed by source, or InvalidArguments carrying DRF's detail.
validated ¶
The values, validated_data as the call's data, and the bound serializer.
validated_many ¶
validated_many(
rows: Mapping[int, Mapping[str, Any]],
context: ValidationContext,
*,
many_argument: str,
) -> Validated
The rows validated once as DRF's list serializer, the one serializer bound for the call.
SerializerPresenter ¶
Bases: Presenter
An operation's output declared as a DRF serializer, rendered through it.
``output()`` reads the serializer's fields into the kernel's
[`Output`][django_service_specs.output.output.Output], in declaration order,
the way drfs' own output schema and audience walk read them:
- A ``write_only`` field is never rendered, so it is not declared.
- **Type**: JSON's own, with ``decimal``, ``date-time`` and ``date`` as
formats. A nested serializer is an object carrying its own ``Output``; a
``many=True`` serializer, and a ``ListField`` whose child is one, is an
array of that ``Output``; a ``MultipleChoiceField`` is an array of its
choices. A field DRF gives no type, a
``SerializerMethodField`` or a ``JSONField`` say, is declared ``any``,
so a schema describes it with no ``type`` rather than as a string it may
not be.
-
Label: the author's, when one was written. DRF derives
Vat idfromvat_idwhen none was, and that isNonehere, as it is notitlein drfs' schema.- Choices: every
(value, display)pair aChoiceFieldholds, typed by the values when they share a type. - Presence:
always_presentis true where DRF is bound to emit the key: a required field, one with a default, and one allowingnull. A read-only field with neither is dropped when DRF cannot read it. Undershaped=True, no field at any depth is: the spec compiles a shape, an argument such as afieldsselector that the serializer reads off the request, and a key is always present only if it is present under every value the caller may send. A drfsQueryParamdeclares no choices, so the values are any string, and which keys each one drops is the serializer's to decide when it renders, so the keys present under all of them are none that can be named. The default,False, is the presence above. - Marking: the
FieldMarkingfound in the field'sstyle, underMARKINGor any other key, and a value underMARKINGthat is not one refused, as drfs' walk reads it. Nested outputs carry their own.
field_audiencesis the spec's own output declaration, a field name to aFieldMarking, layered over the serializer's markings at the top level only, as drfs'overrides=always has been. The layered marking is the compiledOutputField.marking, which is the seataudience_projection_for_specreads, so every engine projects the one declaration with nooverrides=, which stays a mount's. A name that is no output field is refused when the output is read: a hidden marking that silently applies to nothing is a field shown to an agent.presented(value, context)is what the kernel'spresentasks for: the serializer's.datafor one value, under the serializer context derived from the call'sPresentationContext. Therequestandviewthe call registered as pool seeds, read fromambient, are the serializer's own, and where a seed is absent orNone, the entry of that name incall_context; a call that carries neither gets an offline request whoseuseris the principal, so aSerializerMethodFieldreadingself.context["request"].userrenders for the principal the call ran for, and whose query string is the call'sshapes, so a serializer reading afieldsselector offrequest.query_paramsfinds the one the caller sent. A request the call carries is left as it is. A list result is presented row by row by the kernel'spresent, each row with the same context, which is what amany=Truerender is. Over that, the output half of the view hooks a DRF edge adds to the result's context (output_view_layers), resolved with the value, thenserializer_context, the spec's ownoutput_serializer_context, each winning on a key it shares with what it layers over (seeserializer_contextinservice_specs/utils.py). The callable declares what it reads from the call's pool,request,view,useror a registered seed, and the value being presented undervalue_keyword:resultfor a service's output,instancefor a retrieve. It is asked once per value presented, so for a list or amanyservice's output once per row, and no keyword names the row then (value_keywordisNone), nor is the view's hook handed it: the kernel presents a set row by row, and the whole set a callable may declare, a list'spageor amanyoutput'sresult, is never in hand here.present(value)is the context-free form, and delegates topresented(value, None): aNonecontext is the serializer context drfs describes a serializer with (request,viewandformatpresent, eachNone), so aget_fieldsreadingself.context["request"]neither raises there nor sees a principal. It is what aDispatchResulta transport built by hand is presented with, and with no call to resolve it against,serializer_contextis not asked (test_present_with_no_call_runs_no_callable).A dataclass type is read and rendered through the
DataclassSerializerdrfs renders it with, and aBaseSerializerthat is not aSerializerdeclares no fields, so its output is empty while it still presents. - Choices: every
output ¶
The serializer's fields as an Output, with field_audiences layered on. Never queries.
present ¶
One value as the serializer's .data, with no call to render for.
presented ¶
One value as the serializer's .data, under the context of the call it belongs to.
None is presented as None: it is JSON's null, and a serializer
handed none renders a row of blank fields indistinguishable from a real one.
A serializer refusing to render, DRF's ValidationError, leaves as
the dual-base
InvalidArguments,
raised from it, so a route over the kernel's present reads the
kernel's refusal of the arguments, a read-shaping fields naming no
field among them. A mapping detail is kept as it is, and a list, which
is all DRF makes of a message, goes under the kernel's
NON_FIELD_ERRORS; every message keeps its code. One that is
already the kernel's InvalidArguments leaves as raised. No route of
this package's own presents through here, so a live view and
render_spec_output still answer DRF's class
(tests/service_specs/test_a_presenter_s_refusal.py).
DRF permission classes off HTTP¶
DRFPermissionCheck asks each class in order against the offline request and
view built for the principal. The first refusal raises
NotPermitted
with that class's message and code.
Before the class-level check, view.kwargs is filled from the call's arguments
with what a route would have captured: every declared UrlKwarg, taking its
default when the argument is absent or null, and the arguments the spec's
row lookup reads. A declared QueryParam goes to request.query_params. The
object check asks only about model instances. A create or a collection is
never asked about an object, as on HTTP.
A class that raises its own refusal rather than answering False is asked
inside translate_drf_refusals: its
PermissionDenied leaves as NotPermitted with its message and code, and its
NotAuthenticated as DRFNotAuthenticated, which DRF still answers 401.
DRFPermissionCheck ¶
Bases: PermissionCheck
DRF permission classes asked the kernel's three questions, off HTTP.
The kernel asks a principal and a spec; a DRF permission class asks a
request and a view. So each question asks every class in order, a fresh
instance each time, as a DRF view's get_permissions does, against the
request and view the call carries, else the offline pair
build_offline_context
builds for the principal. The first refusal
raises the dual-base
NotPermitted
with that class's own message and code, so a client is told what
a DRF view would have told it; returning False instead would leave the
kernel to raise its own class with the generic wording. A class that
raises its own refusal is asked inside
translate_drf_refusals,
so DRF's PermissionDenied leaves as that NotPermitted with the
class's message and code, and NotAuthenticated as
DRFNotAuthenticated,
which DRF still answers 401
(tests/service_specs/test_drf_refusals_on_the_kernel_route.py).
The view's kwargs are filled from the call's arguments before the
class-level check. A permission scoping by a route capture reads
view.kwargs["project_pk"] in has_permission, and off HTTP there is
no URLconf to put it there; asked against an empty mapping it would refuse
every caller it means to admit. Each method opts in to the kernel's
arguments channel by declaring the keyword, and the names that go onto
view.kwargs are the ones a route would have captured: every declared
UrlKwarg, and route_reads, the arguments the spec's row lookup
reads (pk of an update's instance lookup, or of a retrieve), which
as_kernel_spec
passes in. Each declared QueryParam goes onto request.query_params
instead, where a query string would have put it. A UrlKwarg or
QueryParam the call left out, or sent as null, takes its declared
default, as its docstring says a transport seeds it, and is otherwise
absent. Where a transport asks with no arguments at all (None), nothing
is filled, and the classes decide what an empty route means, as they would
for a URL that captured nothing.
The request and view the call carries come first. The class-level
and object-level methods declare request and view, so a call
registering either as a seed hands it over, and call_context, so a
call that puts either in its context does: a transport running the
kernel's dispatch carries the request a run arrived on, or a role
view built for the operation, that way. Each is the seed, else the
context's entry, and None in either place is no value. A carried
view, or DRF Request, is asked as it is, with nothing filled onto it. Where the call
carries one and not the other, the missing half is the offline pair's,
built as below, so no class is ever handed None for a request or a
view; where it carries neither, the whole offline pair is asked. The
offline view's action is the call's context's action, where that
is a non-empty string, so a class branching on view.action reads the
operation's name; a carried view keeps its own. A carried Django
request that DRF has not wrapped, the request a run arrived on as the
kernel's call context holds it, is asked as a DRF Request around a
copy of it, built as the offline pair is: its user the principal and
its query string the declared QueryParam values the call sent,
replacing its own, which is the endpoint's the run arrived on. Asked with
no arguments at all, nothing is routed and its own query string stays. On
drfs' own edge the kernel asks no check, since dispatch_spec runs
under the Grant its view's permission classes produced.
has_object_permission asks only about a model instance, as
enforce_permissions
always has off HTTP; any other target is allowed here, because a DRF class
written for a row reads attributes off it.
has_scope_permission allows. The kernel asks it of a bulk call's set,
a collection's rows and a create's None, and DRF has no such question:
on HTTP, a create and a collection action run the class-level check and no
object check, and a DRF has_object_permission handed a QuerySet or
None would raise AttributeError or decide on the wrong object. The
class-level check has already run, with the view's kwargs filled, by
the time the kernel asks this one.
No state is kept between questions: the compiled spec holding this check is shared by every call and every thread, so each question builds its own request and view.
has_permission ¶
has_permission(
principal: Any,
spec: Any,
*,
arguments: Mapping[str, Any] | None = None,
call_context: dict[str, Any] | None = None,
request: Any = None,
view: Any = None,
) -> bool
Whether every class's has_permission admits principal, raising on the first that does not.
has_object_permission ¶
has_object_permission(
principal: Any,
spec: Any,
target: Any,
*,
arguments: Mapping[str, Any] | None = None,
call_context: dict[str, Any] | None = None,
request: Any = None,
view: Any = None,
) -> bool
Whether every class's has_object_permission admits principal to a model target.
has_scope_permission ¶
has_scope_permission(
principal: Any, spec: Any, scope: Any, *, arguments: Mapping[str, Any] | None = None
) -> bool
Allows: DRF has no question for a set or a create, as the class docstring says.
NotPermitted ¶
Bases: NotPermitted, PermissionDenied
A permission check refused the principal, as both the kernel's NotPermitted and DRF's PermissionDenied.
Every published consumer of this package catches DRF's
PermissionDenied by name and ends the attempt on it, and DRF's
exception handler answers it 403. None of them catches the kernel's class,
so a denial dispatch makes leaves as this, and an except written
against either side catches it.
Deliberately not a service error, which the kernel's class is not either: an agent reads a service refusal as something to route around and keeps trying, where a denial should end the attempt.
message is the kernel's, and detail is the same words as DRF's
ErrorDetail, coded "permission_denied" unless code is given.
Both __init__s run by name, so neither depends on the order of the
bases, which is a choice (the kernel's first, as the class is named) and
no test can tell from the other. The kernel's sets message:
test_the_default_is_the_kernels_message fails without it. DRF's sets
detail with the code given, which the kernel's super() chain
never passes: test_a_code_given_is_kept fails without it.
build_offline_context ¶
build_offline_context(
user: Any,
params: Mapping[str, Any] | list[Any] | None = None,
*,
http_request: HttpRequest | None = None,
action: str | None = None,
kwargs: Mapping[str, Any] | None = None,
query_params: Mapping[str, Any] | None = None,
host: str | None = None,
) -> OfflineContext
Build the
OfflineContext for
dispatching a spec outside an HTTP request.
dispatch_spec
forwards request / view to spec callables (kwargs providers,
extend_queryset, context providers) that declare them, and
enforce_permissions
needs a request + view to evaluate permission_classes. This synthesizes both so
a spec written for the HTTP transport keeps working when driven from a Pydantic-AI
toolset, the MCP server, a management command, or a task runner.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user
|
Any
|
Set as |
required |
params
|
Mapping[str, Any] | list[Any] | None
|
Seeds |
None
|
http_request
|
HttpRequest | None
|
An ambient Django request to wrap, so its headers / |
None
|
action
|
str | None
|
The view action name, exposed on the
|
None
|
kwargs
|
Mapping[str, Any] | None
|
The channel for route-derived values — the off-HTTP counterpart of a
view's URL captures.
|
None
|
query_params
|
Mapping[str, Any] | None
|
Seeds the request's |
None
|
host
|
str | None
|
The origin the synthesized request reports, so |
None
|
The
| Type | Description |
|---|---|
OfflineContext
|
|
OfflineContext
|
to hand to |
The kernel's refusals as DRF's¶
The kernel raises its own InvalidArguments, NotPermitted and
PrincipalUnavailable, which neither DRF's exception handler nor a transport
catching DRF's ValidationError or PermissionDenied knows. The edge runs the
kernel inside translate_dispatch_errors(), so what dispatch_spec and
adispatch_spec raise for a kernel refusal is one of the three classes below,
each both the kernel's refusal and DRF's: an except written against either
package catches it, detail carries ErrorDetails with their codes, and DRF
answers it 400 or 403. The one refusal that is DRF's alone is a many spec's
rows sent as a bare list, answered as DRF's list serializer answers them, with
a plain rest_framework.exceptions.ValidationError: a body that is no list, a
refused row, a row's undeclared key, or more rows than the bound. So catch
DRF's ValidationError for every refused argument.
A caller running the kernel's dispatch, resolve_target or
shape_queryset itself, or calling
apply_queryset_shaping, gets the
kernel's classes and wraps the call in the same block where it wants DRF's.
A deactivated principal is PrincipalUnavailable, a 403.
translate_dispatch_errors¶
translate_dispatch_errors ¶
Re-raise the kernel's DispatchErrors inside the block as this package's dual-base classes.
Every published consumer of this package catches DRF's
ValidationError and PermissionDenied by name, and DRF's exception
handler answers only those; the kernel's own refusals reach none of them,
and a view serves one as a 500. So the DRF edge runs the kernel's dispatch
inside this block:
- the kernel's
InvalidArgumentsleaves asInvalidArguments, with the same tree and every message's code; - its
NotPermittedasNotPermitted, and itsPrincipalUnavailableasPrincipalUnavailable, each with the same message; - a subclass of any of the three leaves as that class's twin, with the subclass's message.
The new exception is raised from the kernel's, so the traceback keeps
where dispatch refused. Each dual-base class is still the kernel's, so an
except DispatchError around the block catches what leaves it as well.
Left exactly as raised:
- anything that is not a
DispatchError, a service's own refusals among them; - a
DispatchErrorthat is already a DRF exception, such as a dual-base class a validator raised itself, which both ladders already catch. An author callable's DRFPermissionDeniedandNotAuthenticatedreach here as such a class, translated bytranslate_drf_refusalsas the compile hands the kernel the callable, and the DRF edge restores the author's exception itself (itsValidationErrorreaches here as a service's refusal, not aDispatchError); - a
DispatchErrorwith no twin here, which keeps the kernel's class rather than be given a DRF answer nobody declared.
Use it as a with block, around await too. Not as a decorator on an
async def: that would wrap the creation of the coroutine, not its run.
InvalidArguments¶
InvalidArguments ¶
Bases: InvalidArguments, ValidationError
The arguments were refused, as both the kernel's InvalidArguments and DRF's ValidationError.
Every published consumer of this package catches DRF's ValidationError
by name - an MCP server answering "invalid arguments", an agent tool asking
the model to retry - and DRF's exception handler answers it 400. None of
them catches the kernel's class, so what dispatch refuses leaves as this,
and an except written against either side catches it.
Deliberately not a service validation error, which the kernel's class is not either: a business rule stated by a service about well-shaped input is a different refusal, and an MCP server answers the two differently.
detail is the kernel's tree - fields, nested objects, rows under their
int index - with every message an ErrorDetail. A message that
carries a code (the kernel's CodedMessage does) keeps it, and one
that carries none takes code, "invalid" unless given.
DRF's __init__ runs on purpose. By the order of the bases the
kernel's runs, and it leaves detail as plain strings: DRF's handler
still answers 400 with a byte-identical body, but get_codes() raises,
so a test asserting the body passes on the broken class.
test_get_codes_keeps_the_kernels_code_and_defaults_the_rest and
test_every_message_is_an_error_detail are what fail without it.
PrincipalUnavailable¶
PrincipalUnavailable ¶
Bases: PrincipalUnavailable, PermissionDenied
The principal cannot act, as both the kernel's PrincipalUnavailable and DRF's PermissionDenied.
Raised for an identifier naming nobody or a deactivated account, before
any permission check. It reaches every published consumer's ladder as the
denial DRF names, answered 403 and ending the attempt, and an except
written against the kernel's class catches it too.
Deliberately not a service error, which the kernel's class is not either: a refused principal is terminal, never something to route around.
message is the kernel's, and detail is the same words as DRF's
ErrorDetail, coded "permission_denied" unless code is given.
The default is the kernel's wording, not DRF's, since there is nobody to
refuse a permission to.
Both __init__s run by name, as NotPermitted's do, so the order of
the bases is a choice no test can tell from the other. The kernel's sets
message: test_the_default_is_the_kernels_message_not_drfs fails
without it. DRF's sets detail with the code given, which the
kernel's super() chain never passes: test_a_code_given_is_kept
fails without it.
register_drf_row_errors¶
A nested write's row service written for DRF refuses its row with DRF's
ValidationError. Registered, which dispatch_spec's module does when it is
imported, that refusal lands at its row, {"books": {1: {...}}}, with its
codes, and is still DRF's ValidationError.
register_drf_row_errors ¶
Register DRF's ValidationError as a refusal the kernel re-roots under the row that raised it.
A nested write runs a row service per related row, and one written for DRF
refuses its row with DRF's ValidationError, which the kernel cannot
name, so it would otherwise escape the write naming no row. Registered,
it is re-addressed as the kernel's own refusals are - a collection's row
under its int index, a message about the row itself under
non_field_errors - and rebuilt as DRF's ValidationError, with every
message's code kept, so DRF still answers it 400.
Process-wide and for the life of the process, as the kernel's registry is. Running it again does nothing, because the reader is one module-level function; a second registration of the class with any other reader is refused by the kernel.
Not a home for this package's dual-base InvalidArguments: the kernel
reads its own InvalidArguments before any registered class, so that
one leaves a nested row as the kernel's class, and reaches DRF's ladders
through
translate_dispatch_errors.
An author's DRF refusals as the kernel's¶
The reverse direction. An author writes a service, a selector, a precondition,
a kwargs or input_data provider, an extend_queryset or a permission class
against DRF, and refuses with DRF's exceptions, which a transport over the
kernel's dispatch reads as nothing it knows. So each of those runs inside
translate_drf_refusals(), and on the kernel route
(dispatch(kernel_spec(spec))) a refusal leaves as the kernel's class, still
DRF's:
| Raised by the author | Leaves the kernel route as | DRF answers it |
|---|---|---|
ValidationError |
DRFServiceValidationError, a kernel ServiceValidationError, same detail and codes |
400 |
PermissionDenied |
NotPermitted, the kernel's denial, with its message and code |
403 |
NotAuthenticated |
DRFNotAuthenticated, the kernel's denial |
401 |
A project's subclass of any of them keeps its detail and status_code. A
refusal that is already the kernel's leaves as raised, and so does any other
exception, DRF's NotFound and Throttled among them. A progress_reporter
is not run inside it, since it reports and refuses nothing. A DRF refusal raised
from any of these still reaches the kernel route as DRF's class, because the
kernel or a Presenter runs them itself:
- an
input_serializer_contextprovider, which runs inside the Validator, and anoutput_serializer_contextprovider, which runs inside the Presenter; - an affordance's
whencallable; - the methods of a selector spec's
filter_set, which the kernel binds. A service's output selector is the exception while the call narrows its re-read (output_filter_from_call, the default): that filter runs inside the compiledextend_queryset, so its refusals are translated; - a refusal a generator service raises while it is iterated, since the service returned the generator and the iteration is the caller's.
The compiled callables are wrappers around the author's. Identity is not kept
(inspect.unwrap reaches the author's own), and a function's or method's name,
qualname, module, docstring, attributes, annotations and signature are, with
whether it is async. A functools.partial or a callable object gets neither
its docstring nor its attributes copied, so a reader that wants the author's own
docstring for one of those reads it off inspect.unwrap(...).
Over HTTP and through dispatch_spec, nothing changes. The edge re-raises
the exception the author raised before it reads the refusal, with the context
it was raised in: a view answers the status and body it answered before, and a
dispatch_spec caller catches DRF's own class, keyed by the names the request
used. A refusal drfs raised itself, such as a permission class answering
False, is not one the edge restores.
The Presenter answers a serializer refusing to render the same way, as the
dual InvalidArguments: a mapping detail as it is, a list under
non_field_errors. No route of this package presents through it, so only the
kernel's present sees that.
translate_drf_refusals¶
translate_drf_refusals ¶
Re-raise the DRF refusals raised inside the block as the kernel's classes, still DRF's.
The reverse of
translate_dispatch_errors:
an author writes a service, a selector, a precondition or a permission
class against DRF and refuses with DRF's exceptions, which a transport
over the kernel's dispatch reads as nothing it knows. So the compile
runs each author callable it hands the kernel inside this block, and
DRFPermissionCheck
asks each permission class inside it:
ValidationErrorleaves asDRFServiceValidationError, with the same detail and codes;PermissionDeniedasNotPermitted, with its message and code;NotAuthenticatedasDRFNotAuthenticated, which DRF still answers 401;- a subclass of any of the three as that class's twin, with the subclass's
detail and
status_code.
Each is still DRF's class, so an except written against DRF's still
catches it, and DRF's handler answers it with the status the author's
exception had. It is raised from the author's, which it carries under
TRANSLATED_FROM too, so the DRF edge re-raises the exception itself
and every route that answered DRF's exception answers it as before.
Left exactly as raised:
- a refusal that is already the kernel's, drfs' dual-base classes and a twin translated by an inner block among them, which a kernel route already reads;
- any other exception, DRF's
NotFound,AuthenticationFailedandThrottledincluded, which have no kernel class to leave as.
Use it as a with block, around await too, for the reason
translate_dispatch_errors gives.
translating_drf_refusals¶
translating_drf_refusals ¶
fn, its DRF refusals leaving as the kernel's classes; None for no callable.
What the compile hands the kernel in place of each author callable that
runs within a call: the service or selector, the preconditions, the
kwargs and input_data providers and extend_queryset. fn is
called as it was, inside
translate_drf_refusals.
It keeps what a reader of the compiled spec takes from the callable: the
name, qualname, module, annotations and signature, and, of a function or a
method, the docstring and the attributes (a tool description is read off
the docstring). An async def stays one, since the kernel awaits it by
its kind (test_an_async_service_s_refusal_is_translated_too); the signature
is fn's, which the kernel binds the call by; the annotations are
fn's and __wrapped__ is fn, so a provider's return
TypedDict is resolved in its own module and still names the keys it
fills (tests/service_specs/test_translating_drf_refusals.py).
Identity is not kept: the compiled callable is a wrapper, so
inspect.unwrap is what reaches the author's own. A functools.partial
or a callable object gets neither docstring nor attributes, as its
__doc__ is its class's and a reader that tells a function from an
object by inspect.isfunction would otherwise be handed that class's
words. fn itself is never stamped, as it is the author's own function.
DRFServiceValidationError¶
DRFServiceValidationError ¶
Bases: ServiceValidationError, ValidationError
A service refused its input, as both the kernel's ServiceValidationError and DRF's ValidationError.
What an author callable's DRF ValidationError leaves the kernel route
as, through
translate_drf_refusals:
a service, a selector, a precondition, a provider or an
extend_queryset refusing well-shaped input by a rule of its own. A
transport reading the kernel's classes answers it as the service's
refusal, and an except written against DRF's still catches it.
Named apart from the package root's ServiceValidationError, which is
the kernel's own class re-exported, so neither is mistaken for the
other. Deliberately not an InvalidArguments: the kernel's refusal
of the arguments' shape is a different refusal, which an MCP server
answers differently.
detail is DRF's, any shape, with every message an ErrorDetail
keeping its code; message is the kernel's, the detail where that is a
string and the kernel's default otherwise. DRF makes a list of a single
message, so a refusal translated from DRF's carries the default
(test_a_translated_message_is_the_kernel_s_default).
Both __init__s run by name, as NotPermitted's do. The kernel's
sets message: test_a_string_detail_is_the_message fails without it.
DRF's makes the detail ErrorDetails with the code given, which the
kernel's super() chain never passes: test_a_code_given_is_kept fails
without it.
DRFNotAuthenticated¶
DRFNotAuthenticated ¶
Bases: NotPermitted, NotAuthenticated
No principal was authenticated, as both the kernel's NotPermitted and DRF's NotAuthenticated.
What an author callable's or a permission class's DRF NotAuthenticated
leaves the kernel route as, through
translate_drf_refusals.
A transport reading the kernel's classes ends the attempt on it as a
denial, and DRF's exception handler still answers it 401, with the
authenticator's challenge, rather than the 403 the dual
NotPermitted
would be.
message is the kernel's, and detail is the same words as DRF's
ErrorDetail, coded "not_authenticated" unless code is given.
The default is DRF's wording, not the kernel's: the refusal is that
nobody signed in, which the kernel's generic denial does not say
(test_the_default_is_drf_s_message).
Both __init__s run by name, as NotPermitted's do. The kernel's
sets message: test_a_message_given_is_the_kernel_s fails without it.
DRF's sets detail with the code given, which the kernel's
super() chain never passes: test_a_code_given_is_kept fails without it.
Compiling¶
as_kernel_spec ¶
as_kernel_spec(
spec: Any,
*,
partial: bool | None = None,
spread: bool = False,
seeded: frozenset[str] = frozenset(),
) -> KernelServiceSpec | KernelSelectorSpec
Compile spec, a drfs ServiceSpec or SelectorSpec, to the kernel's.
What each drfs spec's own as_kernel_spec() answers, so the kernel's
entry points - dispatch, authorize, the registry and the schemas -
take a drfs spec as it is; they read it through kernel_spec, which
compiles it once per object. This function compiles afresh on every call.
Each field lands on one seat:
input_serializerbecomes theSerializerValidator, and the output serializer theSerializerPresenter, with the spec'sfield_audienceslayered over the serializer's markings. AServiceSpec's output serializer is itsoutput_selector_spec's; one whoseselectorisNonere-reads nothing, so it compiles to no output selector, and its serializer is the compiled spec's own presenter. Where that output is aLISTand the spec is notmany, the service returns the rows, which compiles toreturns_many=True: the kernel presents the return row by row and describes it as an array of the row.permission_classesbecomes oneDRFPermissionCheck; an empty sequence, which drfs reads as "no permissions, explicitly", becomes[Unrestricted()], the kernel's spelling of the same;Nonecompiles to no permissions, which the kernel's registry and dispatch refuse, so a drfs spec reaches a transport without a view only guarded.readsis what the selector and the target lookup take, and underspreadwhat a service with noinput_serializertakes, read off their signatures asspec_to_json_schemareads them: named parameters and the keys of anUnpack-edTypedDict, withInputRequiredread as required and aNotClientInputkey on any callable of the call left out. A named parameter with no default is required too, as the callable raisesTypeErrorwithout it, unless akwargsprovider that says nothing of its keys may fill it or the callable is a wrapper that fills its own (_reflected). A row handed in asdispatch(target=...)does not excuse its lookup's key on this compile: the kernel checks the arguments before it reads the target, so a caller handing one still sends the key. The DRF edge's per-request variants (kernel_spec_variant) leave optional exactly the reads this rule requires (required_for_having_no_default), so a view handing the row it resolved, and each refusal stage the edge documents, answer as they always did. A misplaced marker is refused here, so at registration. Each declaredUrlKwargandQueryParamin the spec's ownreadsis a Parameter too, winning over a reflected one of its name, required where the read says so or the parameter it replaces is (a read declaring adefaultis not:_merge), and a read unless it is a shape (below); one the target lookup reads joins the lookup's reads. Every read taken off a signature is markedreflectedand a declared one is not, so the kernel fills a registered pool seed's name in the former on every dispatch,kernel_spec(spec)'s included, and still refuses the latter as an argument meeting a seed (test_the_kernel_fills_a_selector_s_reflected_read_from_the_seed, test_a_declared_read_named_like_the_seed_is_still_refused_by_the_kernel).shapesis each declaredQueryParamof the top-level spec that no callable of the call takes by name: not the service or selector, a precondition, a target lookup, akwargsorinput_dataprovider, a bare**kwargsor afilter_setfield. It shapes what is rendered, as a serializer reading?fields=does, so the kernel hands it to the presenter alone, onPresentationContext.shapes, rather than to a callable. One a callable names stays inreads, and so does a nested spec's own. A name thefilter_setreads is the filter's, as the filter field always won the schema. The pool's own names (user,request,view,serializerand the kernel's reserved seeds) are never arguments, and a declared one is refused. So is a declared read the service or selector, or one of itspreconditions, marksNotClientInput: dispatch would hand that callable the client's value under the name it keeps the server's. A name only a target lookup hides is not refused; the lookup is never handed it, and the read is the service's argument. A route capture no signature names and noUrlKwargdeclares is no argument at all, so it no longer reaches a spec.- The rest carries by name:
atomic,idempotent,many,many_argument,allow_none,affordances,kwargs,input_data,preconditions,progress_reporter,metadata,select_related,prefetch_related,annotations,filter_set.manytakes the bounds of the list serializer DRF builds for theinput_serializer.extend_queryset, which drfs calls as(queryset, view, request), is wrapped so the kernel, which binds it by name, calls it the same way;viewandrequestare then the pool's registered seeds. ASelectorSpec'saffordancesmap to the compiled form of eachServiceSpec.
partial overrides the spec's own for the variant the DRF edge builds
per request (PATCH); None reads the spec's, where only True is
partial. A spec with no input_serializer has nothing to relax, so it
compiles unpartial.
spread is ArgumentBinding.SPREAD_AUTHOR_WINS: a service with no
input_serializer then takes the caller's values for its own
parameters. Without it, as AUTO and BUNDLE dispatch a service, a
caller fills only what the Validator, the target lookup and the declared
reads read, so def destroy(*, instance, hard=False) is never handed
a hard the request sent (see _takes_its_signature).
seeded names seeds a dispatch registers beyond the pool's own, and
leaves a signature's parameter of such a name - the selector's, a target
lookup's, a spread service's - out of the compiled reads altogether
(test_seeded_compiles_a_name_out_of_a_selector_s_reads_a_marked_read_stays,
test_seeded_compiles_a_name_out_of_a_target_lookup_s_reads,
test_seeded_compiles_a_name_out_of_a_spread_service_s_reads). Without it
the parameter is a reflected read, which the kernel fills from a
registered seed of its name on every dispatch but which stays among the
compiled reads. The DRF edge no longer passes seeded. A declared read
of the name stays one, which the kernel refuses as an argument meeting a
seed.
What the kernel refuses at construction is stripped or refused here,
naming the drfs field. Stripped, because drfs never read them: an output
selector's kwargs, an instance lookup's allow_none (the kernel would
resolve a missing row to None, an upsert, where drfs answers 404), a
nested spec's permission_classes and a target lookup's
output_serializer. Refused, as drfs refuses them at as_view(): a
nested selector's preconditions and progress_reporter, which would
never run. Also refused: a selector of None anywhere but an output
selector or an instance lookup, which compiles to no lookup, and field_audiences with no output serializer to layer them
over.
success_status, document_service_error and response_finalizer
are the HTTP views', and none of them is carried. input_serializer_context
rides on the SerializerValidator and output_serializer_context on the
SerializerPresenter, which hands it the value it presents as result
for a service's output and as instance for a retrieve.
kernel_spec_variant ¶
kernel_spec_variant(
spec: Any,
*,
partial: bool | None = None,
unguarded: bool = False,
target: bool = False,
server_input: bool = True,
spread: bool = False,
lenient: bool = False,
excluded: frozenset[str] = frozenset(),
) -> KernelServiceSpec | KernelSelectorSpec
spec compiled as one request of the DRF edge dispatches it, once per variant.
With every keyword at its default this is kernel_spec(spec), the one
compiled spec every kernel entry point reads. Each other combination is
compiled once per source object and kept while it lives, so a request
never recompiles and a Grant minted for a variant covers it on the
next call too.
Each other combination is the edge's, and it leaves optional every read
kernel_spec(spec) requires only for having no default: the edge fills
such a parameter from a row a view hands in, and refuses a missing one at
the stage it documents, the target lookup's or the service's own
(test_the_edge_s_variant_relaxes_exactly_the_reads_the_rule_asks_for).
InputRequired, a required TypedDict key and a declared read saying
required=True stay required; a declared read that does not is relaxed
as the parameter it names would be.
partial: the per-request flag aPATCHsets, compiled byas_kernel_spec;Nonereads the spec's own.unguarded: nopermissions. An HTTP view has already run DRF'scheck_permissions, so the edge handsdispatchaGrantand the compiled checks would only be asked again against a synthesized request.target: a service spec that declares no target lookup gets a stand-in instance lookup, so the edge can handdispatch(target=...)the row the view'sget_object()resolved; the kernel refusestarget=on a spec with no target selector. The stand-in reads no arguments and is never called: a dispatch reaching it raisesImproperlyConfigured. A spec with a target lookup keeps its own.server_input:Falsecompiles the spec with nokwargsprovider of its own and, on a service spec, noinput_data: the DRF edge evaluates both itself and merges what they answer into the arguments, and a service spec'skwargsseat hands back the author values the edge call carries (author_values) instead. Leaving either on would call the provider a second time (test_a_provider_runs_once_per_call, test_the_input_data_provider_runs_once_per_call).spread: a service spec with noinput_serializertakes the caller's values for its own parameters, as an explicitArgumentBinding.SPREAD_AUTHOR_WINSdispatches it. Off by default, asAUTOandBUNDLEdispatch a service, sokernel_spec(spec)declares none of them.lenient: a service spec's Validator declares its fields by name alone (SerializerValidator(lenient=True)), so the kernel's shape check passes each value through to the serializer. The edge compiles this for a request a live DRF view dispatches, which DRF's serializer has always answered alone: a form's"on"for a boolean,123for a string, every field's refusal at once, in DRF's words. A caller off HTTP keeps the kernel's typed check. Amanyspec's list is unbounded there too, so itsListSerializeralone bounds a live view's body, as DRF's view always did: a body past the kernel'sSERVICE_SPECS_MAX_ARRAY_LENGTHis taken where the list serializer declares nomax_length, and one past a declared bound is refused in DRF's words.excluded: names a service spec's Validator declares no parameter for (SerializerValidator(excluded=...)): the edge compiles the names a seed of the call takes, so a serializer field of that name is the serializer's alone rather than a parameter the kernel refuses.