Audience projection¶
One serializer, more than one kind of reader. These helpers let a field say who it is written for — see the recipe for the task-shaped version — and let an agent transport apply that declaration to payloads and schemas from the same source, so the two cannot disagree.
Nothing here is read by the DRF view path. A serializer marked up for an agent renders byte-identically behind a viewset.
Nor is there any wording for a model here. The markings say what a field is;
what a reader should do about it depends on the reader, and the transport is
what knows — so annotate_output_schema takes the sentence rather than
supplying one.
The value types (FieldAudience, FieldMarking, AudienceProjection, MARKING) are
documented under Types; the render entry points
(render_for_audience / arender_for_audience) under
Dispatch.
audience_projection_for_spec¶
audience_projection_for_spec ¶
audience_projection_for_spec(
spec: ServiceSpec | SelectorSpec | SpecSource,
*,
overrides: Mapping[str, FieldMarking] | None = None,
name: str | None = None,
) -> AudienceProjection
The AudienceProjection of what spec presents.
django-service-specs'
audience_projection_for_spec:
the field markings of the compiled spec's output, with overrides laid
over them, and name naming the projection in a refusal. A
SelectorSpec with no selector is projected from its declaration,
since projecting runs no selector.
build_audience_projection¶
build_audience_projection ¶
build_audience_projection(
serializer_cls: type | None,
*,
overrides: Mapping[str, FieldMarking] | None = None,
name: str | None = None,
) -> AudienceProjection
Resolve one output declaration's agent presentation, recursing into children.
Reads
FieldMarking markings
out of each field's style bag and collects the ChoiceField labels DRF
already holds. Pure in the serializer class, so the result is built once and
reused — see
AudienceProjection.
serializer_cls is read as the class it renders through, resolved with
renderable_serializer_class,
so a bare @dataclass output is projected from the DataclassSerializer
it is rendered with. Two consequences follow, both deliberate. A marking
given to a dataclass field — field(metadata={"serializer_kwargs": {"style":
{MARKING: FieldMarking.hidden()}}}) — is collected like one on a serializer
field, so hidden means hidden whichever way the output was declared. And an
Enum field renders as a ChoiceField whose display values are the
member names, so an agent reads "GOLD" where the browser reads
"gold": the substitution every ChoiceField gets, and mirrored into
the schema by
annotate_output_schema.
Mark the field HANDLE to keep the value for an agent too.
None, or anything else that does not resolve to a DRF Serializer,
yields an empty projection rather than an error: there is nothing to mark up.
overrides layers a caller's markings on top, for the one case the
serializer cannot express: a mount that needs what its sibling hides. They
reach a transport as
OfflineContract.field_audiences,
which is one declaration read by every agent transport — and the merge lives
here, next to the clash rule it extends, so that stays true. Two copies of it
is how one spec comes to project a different field set depending on which
transport served it. name identifies the mount in the error message and
defaults to the serializer's own class name.
Raises:
| Type | Description |
|---|---|
ImproperlyConfigured
|
If a field carries
something other than an |
project_payload¶
project_payload ¶
Drop plumbing, speak choice displays and format values, at any depth.
Three changes, each driven by the declaration that shapes the schema in
annotate_output_schema,
so the two cannot disagree:
- fields marked
HIDDENare removed. Removed rather than moved under some reserved key: a payload a model reads is read in full either way, so moving a field costs its keys again and hides nothing. What an agent must never use should not be there. - a field's choice value is replaced by its display, so a person is not
read
PENDING_REVIEW. Not on aHANDLE, which another tool takes as input. An array of choices is substituted member by member rather than looked up whole. - a field whose marking carries a
ValueFormatteris rendered through it: a date-time read as a local date-time rather than raw ISO-8601, an amount with its currency. It wins over the choice display, being the transform an author wrote by hand, and over a nested output's projection. Also not on aHANDLE, for the same reason, and a null is never formatted.
A payload that is neither an object nor a list of them is returned as it is, and so is every key the projection has nothing to say about, including one a transport added beside the declared fields.
Apply this where a payload becomes an agent's answer, not where it
feeds the next step of a chain, which still needs the handles. Returns a
new payload and never modifies payload.
annotate_output_schema¶
annotate_output_schema ¶
annotate_output_schema(
schema: dict[str, Any] | None,
projection: AudienceProjection,
*,
handle_description: str | None = None,
) -> dict[str, Any] | None
Apply to a schema the projection project_payload applies to the payload.
Four changes, each the mirror of one the payload undergoes:
- hidden properties are removed, and dropped from
required, which is left out when nothing in it remains; - a marking's
descriptionbecomes the property's"description", so a handle says what it is for in the schema a model reads without that wording reaching any other reader; - a field with labelled choices is restated in its displays, because
that is what the projected payload carries: an
enum's values are replaced, and aoneOf'sconsts are, each losing thetitlethat annotated the value it now equals. A"type"stated beside them is restated as the types of the values now listed, so a display string is never described as the integer it replaced, with"null"named last where the stated type admitted it and a null is still served - not where the choices giveNonea display, which is served in its place - and left as written beside aoneOfentry that admits more than its constants. A display two values share is listed once, so a row served it matches oneoneOfentry rather than two, and an array of such choices no longer claimsuniqueItems. Not on aHANDLE, which keeps its values on both sides: a field another tool takes as input should be marked one. - a formatted field is restated as the type its
ValueFormattersays it produces, plus whatever that declaration adds about the shape of the produced value, in place of everything the property said about the value before, choices included. Itstitleanddescriptionare kept, and so is anullthe property admitted - however it stated one, as a type, ananyOfmember,Nonein anenumor aoneOfentry for a labelled choice - since a null is never formatted. The type is written fromproducesrather than taken from the fragment, so a renderer cannot contradict its own advertisement, and a fragment'senumlists the null too, and itsconstbecomes anenumof that value and a null, where the property admitted one. Not on aHANDLEeither.
Generating both sides from one declaration is the point: a schema that advertises a field the payload no longer carries is worse than either behaviour on its own.
handle_description is the wording for a HANDLE whose marking
declares none, and defaults to nothing. Telling a reader what to do
with an identifier is advice for one kind of reader, and the kernel does
not know which kind is reading. The transport that knows its audience
supplies the sentence.
Takes the item schema; an array's items are annotated and the
array kept. A caller wrapping items in an envelope of its own annotates the
item and wraps it afterwards, as
spec_output_schema
does. None, or a projection with nothing in it, returns schema
itself; anything else returns a new schema and never modifies schema.