Module adcp.types.canonical_decl
Wire-faithful ProductFormatDeclaration for the v2 catalog surface.
The upstream schema core/product-format-declaration.json is a
discriminated oneOf over 13 format_kind values, each binding
params to a canonical-specific schema. datamodel-code-generator
collapses this shape to a single class carrying only the shared
properties — format_kind and params disappear entirely because
they live on the per-variant branches.
That generated stub is unusable for canonical-formats: it can't carry
the discriminator the projection layer routes on, and it silently drops
params (extra='ignore') so adopters who construct a declaration
with a typed canonical body lose it on serialization.
This module replaces the public ProductFormatDeclaration symbol
with a hand-rolled class that:
- Carries all 9 shared properties the generator emits.
- Adds
format_kind: CanonicalFormatKind(the discriminator). - Adds
params: dict[str, Any]— the per-canonical body. Required per the upstream schema (required: ["format_kind", "params"]). Kept as an open dict at this level so the same class works across all 13 canonical kinds; callers needing typed access SHOULD use :meth:ProductFormatDeclaration.params_as()to validate against the typed canonical format class. - Sets
extra='allow'so futureProductFormatDeclarationfield additions in 3.1.x don't break round-trip through this model. Extra fields are scanned for credential-shaped key suffixes at construction time; presence of one raises (see_CREDENTIAL_SHAPED_KEY_SUFFIXES). - Enforces the schema's normative cross-field constraint that
canonical_formats_only=Trueandv1_format_ref[]are mutually exclusive (product-format-declaration.jsonallOf.notclause).
The generated class is preserved as _GeneratedProductFormatDeclaration
for callers that need the original codegen output (validation hooks,
schema-loader cross-references). New code SHOULD import the
hand-rolled class via :mod:adcp.types.
Classes
class ProductFormatDeclaration (**data: Any)-
Expand source code
class ProductFormatDeclaration(AdCPBaseModel): """v2 catalog-side format declaration carrying the canonical discriminator. Wire-faithful Python representation of ``core/product-format-declaration.json``. See the module docstring for why this class replaces the codegen output. """ model_config = ConfigDict(extra="allow") format_kind: Annotated[ CanonicalFormatKind, Field(description="The canonical format kind this declaration declares."), ] params: Annotated[ dict[str, Any], Field( description=( "Per-canonical body. Shape varies by format_kind — see the " "canonical's own schema (``formats/canonical/<kind>.json``). " "Use :meth:`params_as` for typed access." ), ), ] capability_id: Annotated[ str | None, Field( description=( "Stable identifier for this declaration. REQUIRED when the " "parent product's format_options[] contains multiple " "declarations sharing the same format_kind." ), ), ] = None display_name: Annotated[ str | None, Field(description="Optional seller-controlled human-readable label."), ] = None applies_to_channels: Annotated[ list[MediaChannel] | None, Field( description=( "Optional subset of the parent product's channels to which " "this declaration applies." ), ), ] = None seller_preference: Annotated[ SellerPreference | None, Field(description="Soft routing hint within the accepted set."), ] = None canonical_formats_only: Annotated[ bool, Field( description=( "When true, this declaration has no clean v1 projection — " "SDKs MUST NOT synthesize a v1 format_id. Mutually exclusive " "with ``v1_format_ref``." ), ), ] = False experimental: Annotated[ bool, Field( description=("When true, THIS seller's specific declaration may not work as declared."), ), ] = False format_shape: Annotated[ str | None, Field( description=( "REQUIRED when format_kind='custom'; otherwise MUST be absent. " "Recognized format-shape-vocabulary entry." ), ), ] = None v1_format_ref: Annotated[ list[FormatReferenceStructuredObject] | None, Field( description=( "Authoritative v2 → v1 link as one or more v1 format_id " "({agent_url, id}) values. Mutually exclusive with " "``canonical_formats_only=True``." ), min_length=1, ), ] = None format_schema: Annotated[ PlatformExtensionReference | None, Field( description=( "REQUIRED when format_kind='custom'; otherwise MUST be absent. " "URI+digest reference to the custom shape's schema." ), ), ] = None @model_validator(mode="after") def _check_mutual_exclusion(self) -> Self: """Enforce the schema's ``allOf.not`` clause. ``product-format-declaration.json`` declares ``canonical_formats_only=True`` and ``v1_format_ref[]`` mutually exclusive. The Pydantic model rejects the combination at construction so the SDK never launders a wire-invalid declaration into a wire-valid one. """ if self.canonical_formats_only and self.v1_format_ref: raise ValueError( "ProductFormatDeclaration: canonical_formats_only=True is " "mutually exclusive with v1_format_ref[] — a declaration can " "EITHER assert no v1 projection OR link to v1 named formats, " "never both. See product-format-declaration.json#allOf.not." ) return self @model_validator(mode="after") def _reject_credential_shaped_extras(self) -> Self: """Fail-closed scan for credential-shaped keys in ``params`` + extras. ``params`` is an open dict and ``model_config['extra']='allow'`` means unknown top-level fields are stored on the instance. Both are adopter-controlled bags that round-trip through ``format_options[]`` responses and the idempotency replay cache. Mirrors the dispatcher's ``ctx_metadata`` credential gate. """ for bag_name, bag_value in ( ("params", self.params), ("extras", self.__pydantic_extra__), ): if bag_value is None: continue found = _walk_for_credential_keys(bag_value, path=bag_name) if found is not None: raise ValueError( f"ProductFormatDeclaration: {found!r} matches a " f"credential-shaped key suffix and will round-trip to " f"buyers via format_options[]. Move the value to " f"AuthInfo.credential or a typed credential class. " f"See CLAUDE.md → 'ctx_metadata: write-only credentials " f"prohibited' for the equivalent dispatch-side rule." ) return self def params_as(self, canonical_type: type[_TypedParams]) -> _TypedParams: """Validate ``params`` against the typed canonical-format class. Lets buyers and seller-side validators recover full typing on the per-canonical body — e.g., ``decl.params_as(CanonicalFormatImage)`` returns a ``CanonicalFormatImage`` with ``.sizes`` / ``.format`` / etc. narrowed. Raises :class:`pydantic.ValidationError` when ``params`` doesn't match the canonical's schema. Args: canonical_type: A Pydantic model class from the canonical vocabulary (e.g., :class:`adcp.types.CanonicalFormatImage`). Returns: An instance of ``canonical_type`` validated against ``params``. """ return canonical_type.model_validate(self.params)v2 catalog-side format declaration carrying the canonical discriminator.
Wire-faithful Python representation of
core/product-format-declaration.json. See the module docstring for why this class replaces the codegen output.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var applies_to_channels : list[MediaChannel] | Nonevar canonical_formats_only : boolvar capability_id : str | Nonevar display_name : str | Nonevar experimental : boolvar format_kind : CanonicalFormatKindvar format_schema : PlatformExtensionReference | Nonevar format_shape : str | Nonevar model_configvar params : dict[str, typing.Any]var seller_preference : SellerPreference | Nonevar v1_format_ref : list[FormatReferenceStructuredObject] | None
Methods
def params_as(self, canonical_type: type[_TypedParams]) ‑> ~_TypedParams-
Expand source code
def params_as(self, canonical_type: type[_TypedParams]) -> _TypedParams: """Validate ``params`` against the typed canonical-format class. Lets buyers and seller-side validators recover full typing on the per-canonical body — e.g., ``decl.params_as(CanonicalFormatImage)`` returns a ``CanonicalFormatImage`` with ``.sizes`` / ``.format`` / etc. narrowed. Raises :class:`pydantic.ValidationError` when ``params`` doesn't match the canonical's schema. Args: canonical_type: A Pydantic model class from the canonical vocabulary (e.g., :class:`adcp.types.CanonicalFormatImage`). Returns: An instance of ``canonical_type`` validated against ``params``. """ return canonical_type.model_validate(self.params)Validate
paramsagainst the typed canonical-format class.Lets buyers and seller-side validators recover full typing on the per-canonical body — e.g.,
decl.params_as(CanonicalFormatImage)returns aCanonicalFormatImagewith.sizes/.format/ etc. narrowed. Raises :class:pydantic.ValidationErrorwhenparamsdoesn't match the canonical's schema.- Args
- -----=
canonical_type- A Pydantic model class from the canonical
vocabulary (e.g., :class:
CanonicalFormatImage).
Returns -----= An instance of
canonical_typevalidated againstparams.
Inherited members