diff --git a/MIGRATION_v9_to_v10.md b/MIGRATION_v9_to_v10.md index 2f42e104a..73b89c56e 100644 --- a/MIGRATION_v9_to_v10.md +++ b/MIGRATION_v9_to_v10.md @@ -27,3 +27,4 @@ the SDK-relevant inventory is [docs/adcp-3.3-sdk-inventory.md](docs/adcp-3.3-sdk | A2A requests must meet the AdCP 3.3 operation-resolution hardening when request signing is configured | Send one JSON-RPC request per body with no duplicate or case-variant member names, one content member per Part, no FileParts, and exactly one DataPart carrying `skill`. Otherwise the request fails with `request_body_malformed`. A `message.taskId` continuation must come from the principal that created the task and name its original operation: a different principal gets task-not-found, and a different operation gets `INVALID_REQUEST`. | | `model_dump()` writes wire names | A field the generator aliased because its wire key is not a Python identifier is emitted under the wire key without `by_alias=True`: `$schema` instead of `field_schema`, `pass` instead of `pass_`, `from` instead of `from_`, `media-buy` instead of `media_buy`, `_truncation` instead of `field_truncation`, `2xl` instead of `field_2xl`. Code that reads a dump by the Python name must read the wire name; pass `by_alias=False` to keep the SDK 9 output. Construction accepts both spellings (`validate_by_name` is on). | | A received `null` is re-emitted; a Python `None` keyword argument is "not supplied" | A property whose schema admits `null` and that arrived as `null` (`model_validate`, `model_validate_json`, nested validation) stays in `model_fields_set` and is written back as `null` by `model_dump()`. Passing `None` as a keyword argument to a constructor, assigning `None` to a field, or passing `None` in `model_copy(update=...)` now leaves the field *unset*: it is omitted from the dump, and `model_dump(exclude_unset=True)` no longer lists it. A `None` inside a dict you validate is a wire `null`. Properties whose schema does not admit `null` are unchanged (`NoExplicitNull` refuses a JSON `null` and never emits one). | +| `model_dump()` leaves an unset schema default off | A property the schema lists outside `required` with a `default` (or a `const`) is emitted only when it was set: by the document that was parsed, by a keyword argument, or by assignment. `sandbox: false`, `timezone: "inventory_local"`, `assignment_limit: 50` and the other schema defaults no longer appear on a dump unless you set them; the attribute still reads the default. Required discriminators (`asset_type`, `pricing_model`) are emitted either way. Pass `exclude_unset=False` for the SDK 9 output. Golden-file tests that compare whole dumps need regenerating. If you relied on a default reaching the peer, set it explicitly. `model_dump_as_received()` is deprecated: it returns the same document as `model_dump(mode="json")` and is removed in SDK 11. Generated root models (a union root such as `LocalizedCreativeAsset`) now subclass `AdCPRootModel` and dump with `exclude_none=True` like every other AdCP model. | diff --git a/docs/validation-contract.md b/docs/validation-contract.md index f085f5582..522b1549d 100644 --- a/docs/validation-contract.md +++ b/docs/validation-contract.md @@ -105,37 +105,32 @@ mechanisms carry it, and a conformance suite measures it: AdCP schema types (`adcp.types.domains`); `adcp.types.registry`, the OpenAPI-generated registry client, keeps pydantic's `AnyUrl` and `AwareDatetime` — no registry object is digest-covered. -- **An explicit "as received" dump.** `AdCPBaseModel.model_dump_as_received()` - is `model_dump(mode="json", exclude_unset=True, exclude_none=False, - by_alias=True)`: it leaves off every field the document did not set (where - a generated default would otherwise appear), keeps an explicit `null` the - schema admits, and writes wire names. Recompute a digest over this dump, not - the default one. For a model built in Python it emits exactly the fields the - caller set. +- **The default dump is the document.** `model_dump(mode="json")` leaves off + a schema-optional default the document did not carry (`OmitUnsetDefault`, + #1613), re-emits a `null` the document carried on a property whose schema + admits one (#1615), writes wire names (`$schema`, `pass`, `from`; #1615), + and keeps an unknown property where the schema object is open (#1614). + Recompute a digest over the default dump. For a model built in Python it + emits the fields the caller set plus the ones the schema requires. + `model_dump_as_received()` is a deprecated alias of the same dump and is + removed in SDK 11; `model_dump(exclude_unset=False)` gives the SDK 9 output + with every default materialized. `tests/conformance/test_wire_round_trip.py` runs every bundle schema that has a model — the bundle's `examples`, a synthesized document, the differential fixtures, a checked-in corpus for the digest-covered schemas, and variants of each exercising every form a normalizing parser rewrites — through `model_validate_json(doc).model_dump(mode="json")` and compares the result to -the input under JCS. A rewritten URL, datetime, UUID, email, value or shape -fails anywhere. The digest- and echo-covered schemas must round-trip with no -difference of any class under `model_dump_as_received`. Under the default -dump the suite names the classes that remain, each with the issue that tracks -it, and the list only shrinks: +the input under JCS. A rewritten URL, datetime, UUID, email, value or shape, +an emitted default, a dropped null or a Python-named key fails anywhere. The +digest- and echo-covered schemas must round-trip with no difference of any +class. Under the default dump the suite names the classes that remain, each +with the issue that tracks it, and the list only shrinks: | class | what happens | where it stands | | --- | --- | --- | -| `default_emitted` | the dump materializes a schema default the document omitted | #1613; `model_dump_as_received` recovers the bytes. Two digest-covered schemas (`canonical-proposal`, `commercial-terms`) are affected under the default dump and are listed in the suite. | -| `field_dropped` | `extra='ignore'` drops an unknown or sibling-arm property | #1614; not recoverable by any dump. No digest-covered schema is affected at pin 3.2.7. | -| `alias_emitted` | `model_dump()` writes `pass_` for `pass`, `field_schema` for `$schema` | #1615; `model_dump_as_received` writes the wire name. | -| `null_dropped` | `exclude_none` drops an admitted explicit `null` | #1615; `model_dump_as_received` keeps it. | - -Why the default dump was not changed in 9.x: leaving unset defaults off every -`model_dump()` is wire-equivalent for a schema-applying reader but visible to -any code that indexes a dumped dict (`payload["sandbox"]`), including a -counterparty that does not apply schema defaults. That is a 10.0 decision, -written up in #1613. +| `field_dropped` | `extra='ignore'` drops an unknown or sibling-arm property | #1614; not recoverable by any dump. No digest-covered schema is affected. | +| `discriminator_emitted` | a document valid under one arm is routed to a sibling arm, whose required single-value `Literal` it does not carry, and the arm's injected default appears | #1414; closed by #1522's construction dispatch. | ## Wire-boundary behavior diff --git a/scripts/generate_types.py b/scripts/generate_types.py index 1592c5f7a..d96d836b3 100755 --- a/scripts/generate_types.py +++ b/scripts/generate_types.py @@ -967,6 +967,14 @@ def flatten_root_object_validation_union(schema: dict, schema_path: Path) -> dic #: carry it. NO_EXPLICIT_NULL_KEY = "x-adcp-no-explicit-null" +#: Pre-generation mark on an optional property whose schema declares a +#: ``default``. Carried the same way, and turned into the +#: ``adcp.types.base.OmitUnsetDefault`` annotation by the same post-generation +#: pass: the default dump then leaves the property off unless it was set +#: (#1613). A required property with a default is not marked; it is emitted +#: either way. +OMIT_UNSET_DEFAULT_KEY = "x-adcp-omit-unset-default" + def _file_uri_resource(path: Path): """Load *path* as a ``referencing`` resource addressed by its file URI. @@ -1034,6 +1042,21 @@ def _required_names(node: Any, resolver, seen: frozenset[str] = frozenset()) -> return required +def _carries_a_default(property_schema: dict[str, Any]) -> bool: + """Whether the generated field for this optional property gets a default. + + A schema ``default`` becomes the field's default. A ``const`` or a one-value + ``enum`` becomes a single-value ``Literal``, and + ``post_generate_fixes.inject_literal_discriminator_defaults`` gives that a + default too; on an optional property that default is a value the wire did + not send, so it is marked the same way. + """ + if "default" in property_schema or "const" in property_schema: + return True + enum = property_schema.get("enum") + return isinstance(enum, list) and len(enum) == 1 + + def _mark_no_explicit_null(node: Any, validator, resolver, counter: list[int]) -> None: if isinstance(node, list): for item in node: @@ -1050,8 +1073,11 @@ def _mark_no_explicit_null(node: Any, validator, resolver, counter: list[int]) - if not _property_admits_null(validator, property_schema): property_schema[NO_EXPLICIT_NULL_KEY] = True counter[0] += 1 + if _carries_a_default(property_schema): + property_schema[OMIT_UNSET_DEFAULT_KEY] = True + counter[1] += 1 for key, value in node.items(): - if key == NO_EXPLICIT_NULL_KEY: + if key in (NO_EXPLICIT_NULL_KEY, OMIT_UNSET_DEFAULT_KEY): continue _mark_no_explicit_null(value, validator, resolver, counter) @@ -1100,6 +1126,7 @@ def retrieve(uri: str): for path in sorted(temp_dir.rglob("*.json")) ) marked = 0 + defaulted = 0 for schema_file in sorted(temp_dir.rglob("*.json")): with open(schema_file) as f: schema = json.load(f) @@ -1117,13 +1144,15 @@ def retrieve(uri: str): validator_class = validator_for(document) validator = validator_class(document, registry=registry) resolver = registry.resolver(base_uri=document["$id"]) - counter = [0] + counter = [0, 0] _mark_no_explicit_null(schema, validator, resolver, counter) - if counter[0]: + if counter[0] or counter[1]: with open(schema_file, "w") as f: json.dump(schema, f, indent=2) marked += counter[0] + defaulted += counter[1] print(f" Marked {marked} optional properties that refuse an explicit null") + print(f" Marked {defaulted} optional properties that carry a schema default") return marked @@ -1391,6 +1420,7 @@ def _run_datamodel_codegen(input_path: Path, output_path: Path) -> subprocess.Co # where the post-generation pass turns it into ``NoExplicitNull``. "--field-extra-keys", NO_EXPLICIT_NULL_KEY, + OMIT_UNSET_DEFAULT_KEY, "--allow-remote-refs", ] diff --git a/scripts/post_generate_fixes.py b/scripts/post_generate_fixes.py index 8abab7ac4..d63fc3cbe 100644 --- a/scripts/post_generate_fixes.py +++ b/scripts/post_generate_fixes.py @@ -1135,6 +1135,12 @@ def point_integer_fields_at_the_schema_integer_type() -> None: #: The ``Field`` keyword the generator emits for ``generate_types.NO_EXPLICIT_NULL_KEY``. _NO_EXPLICIT_NULL_EXTRA = "json_schema_extra={'x-adcp-no-explicit-null': True}" _NO_EXPLICIT_NULL_MARKER = "NoExplicitNull" +_NO_EXPLICIT_NULL_KEY = "x-adcp-no-explicit-null" +#: The second generator mark the same rewrite carries: a schema-optional +#: property with a schema ``default`` (``generate_types.OMIT_UNSET_DEFAULT_KEY``). +_OMIT_UNSET_DEFAULT_KEY = "x-adcp-omit-unset-default" +_OMIT_UNSET_DEFAULT_MARKER = "OmitUnsetDefault" +_MARK_KEYS = frozenset({_NO_EXPLICIT_NULL_KEY, _OMIT_UNSET_DEFAULT_KEY}) def _generated_line_width() -> int: @@ -1196,7 +1202,7 @@ def _no_explicit_null_keyword(call: ast.expr) -> ast.keyword | None: if keyword.arg != "json_schema_extra" or not isinstance(keyword.value, ast.Dict): continue keys = [k.value for k in keyword.value.keys if isinstance(k, ast.Constant)] - if keys == ["x-adcp-no-explicit-null"]: + if keys and len(keys) == len(keyword.value.keys) and set(keys) <= _MARK_KEYS: return keyword return None @@ -1345,8 +1351,16 @@ def _rewrite_no_explicit_null_marks( annotation_names = {n.id for n in ast.walk(elements[0]) if isinstance(n, ast.Name)} | { n.value for n in ast.walk(elements[0]) if isinstance(n, ast.Constant) } - if None in annotation_names and marker is not None: + assert isinstance(keyword.value, ast.Dict) + keys = {k.value for k in keyword.value.keys if isinstance(k, ast.Constant)} + appended = False + if _NO_EXPLICIT_NULL_KEY in keys and None in annotation_names and marker is not None: metadata.append(marker) + appended = True + if _OMIT_UNSET_DEFAULT_KEY in keys and marker is not None: + metadata.append(_OMIT_UNSET_DEFAULT_MARKER) + appended = True + if appended: marked += 1 # Measure from the start of the line: the indentation counts toward # the width black lays the statement out against. @@ -1372,13 +1386,13 @@ def _rewrite_no_explicit_null_marks( return raw.decode("utf-8"), marked -def _ensure_no_explicit_null_import(source: str) -> str: - """Import ``NoExplicitNull`` from ``adcp.types.base`` when the module lacks it.""" - if re.search(r"^from adcp\.types\.base import .*\bNoExplicitNull\b", source, re.MULTILINE): +def _ensure_no_explicit_null_import(source: str, name: str = _NO_EXPLICIT_NULL_MARKER) -> str: + """Import ``name`` (a marker) from ``adcp.types.base`` when the module lacks it.""" + if re.search(rf"^from adcp\.types\.base import .*\b{name}\b", source, re.MULTILINE): return source if re.search(r"^from adcp\.types\.base import ", source, re.MULTILINE): - return add_to_import(source, "adcp.types.base", _NO_EXPLICIT_NULL_MARKER) - statement = f"from adcp.types.base import {_NO_EXPLICIT_NULL_MARKER}\n" + return add_to_import(source, "adcp.types.base", name) + statement = f"from adcp.types.base import {name}\n" anchor = _import_pattern("pydantic").search(source) if anchor is not None: return source[: anchor.start()] + statement + source[anchor.start() :] @@ -1407,10 +1421,12 @@ def point_omittable_fields_at_no_explicit_null() -> None: marked = 0 for path in sorted(OUTPUT_DIR.rglob("*.py")): source = path.read_text() - if _NO_EXPLICIT_NULL_EXTRA not in source: + no_null = f"'{_NO_EXPLICIT_NULL_KEY}'" + omit_default = f"'{_OMIT_UNSET_DEFAULT_KEY}'" + if no_null not in source and omit_default not in source: continue source, count = _rewrite_no_explicit_null_marks(source) - assert _NO_EXPLICIT_NULL_EXTRA not in source, path + assert no_null not in source and omit_default not in source, path # A module whose only ``Field`` or ``Annotated`` was the mark's is left # exactly as the generator writes it without the mark. if "Field(" not in source: @@ -1419,8 +1435,10 @@ def point_omittable_fields_at_no_explicit_null() -> None: source = drop_from_imports(source, "typing", "Annotated") # Dropping the only ``typing`` import leaves its blank line behind. source = re.sub(r"\n{3,}(from |import )", r"\n\n\1", source, count=1) - if count: - source = _ensure_no_explicit_null_import(source) + if re.search(r"\bNoExplicitNull\b", source): + source = _ensure_no_explicit_null_import(source, _NO_EXPLICIT_NULL_MARKER) + if re.search(r"\bOmitUnsetDefault\b", source): + source = _ensure_no_explicit_null_import(source, _OMIT_UNSET_DEFAULT_MARKER) path.write_text(source) modified += 1 marked += count @@ -3837,6 +3855,8 @@ def _ensure_sequence_import(content: str) -> str: r"( canceled: Annotated\[\n )" r"Literal\[True\]" r"(,\n Field\(\n description=[^\n]*\n \),\n)" + # The optional-const mark (#1613) is dropped with the default it qualified. + r"(?: OmitUnsetDefault,\n)?" r"( \])" r" = True" ) @@ -3850,6 +3870,7 @@ def _ensure_sequence_import(content: str) -> str: r"( unchanged: Annotated\[\n )" r"Literal\[True\]" r"(,\n Field\(\n description=.*?\n \),\n)" + r"(?: OmitUnsetDefault,\n)?" r"( \])" r" = True", re.DOTALL, @@ -7440,6 +7461,35 @@ def _rebuild_union(arms: list[ast.expr]) -> ast.expr: return rebuilt +def point_rootmodels_at_adcp_root() -> None: + """Give every generated ``RootModel`` the SDK's dump defaults (#1613). + + A composing root that ``unwrap_rootmodel_unions`` keeps wrapped dumped + with pydantic's defaults -- every ``None`` of its arm as ``null`` -- while + the arm itself, an ``AdCPBaseModel``, left them off. The wrapper now + subclasses ``adcp.types.base.AdCPRootModel``, whose ``model_dump`` and + ``model_dump_json`` carry the base model's ``exclude_none`` and + ``serialize_as_any`` defaults. Runs last: the earlier passes locate root + models by the ``RootModel[`` base this one renames. + """ + pattern = re.compile(r"(class \w+\(\s*)RootModel\[") + modified = 0 + for path in sorted(OUTPUT_DIR.rglob("*.py")): + source = path.read_text() + if "RootModel[" not in source: + continue + rewritten, count = pattern.subn(r"\1AdCPRootModel[", source) + if not count: + continue + rewritten = _ensure_no_explicit_null_import(rewritten, "AdCPRootModel") + body = re.sub(r"^from pydantic import [^\n]*\n", "", rewritten, flags=re.MULTILINE) + if not re.search(r"(? None: """Keep request-signing operation lists source-compatible with beta.5. @@ -8650,6 +8700,7 @@ def main(argv: list[str] | None = None) -> int: # ``__all__`` it writes has to name the final set. declare_root_schema_module_exports, strip_extra_blank_lines_at_eof, + point_rootmodels_at_adcp_root, ] observed: dict[str, list[str]] = {} digest = _tree_digest(OUTPUT_DIR) diff --git a/scripts/post_generation_manifest.json b/scripts/post_generation_manifest.json index c02999b4d..ee509f871 100644 --- a/scripts/post_generation_manifest.json +++ b/scripts/post_generation_manifest.json @@ -78,6 +78,7 @@ "remove_unused_pydantic_field_imports": true, "emit_open_asset_unions": true, "declare_root_schema_module_exports": true, - "strip_extra_blank_lines_at_eof": true + "strip_extra_blank_lines_at_eof": true, + "point_rootmodels_at_adcp_root": true } } diff --git a/src/adcp/reporting/production/offerings.py b/src/adcp/reporting/production/offerings.py index fdd426810..8ea28e515 100644 --- a/src/adcp/reporting/production/offerings.py +++ b/src/adcp/reporting/production/offerings.py @@ -61,8 +61,14 @@ def __post_init__(self) -> None: from adcp.validation.schema_loader import get_named_validator validator = get_named_validator("core/reporting-delivery-offering.json") - if validator is None or next(validator.iter_errors(raw), None) is not None: + if validator is None: raise ValueError("offering must satisfy the complete public contract") + error = next(validator.iter_errors(raw), None) + if error is not None: + where = "/".join(str(part) for part in error.absolute_path) or "/" + raise ValueError( + f"offering must satisfy the complete public contract: {error.message} at {where}" + ) profile, definition, key = ( raw["reporting_profile"], self.verification_key.definition, diff --git a/src/adcp/types/base.py b/src/adcp/types/base.py index 1826258a6..95b05ad46 100644 --- a/src/adcp/types/base.py +++ b/src/adcp/types/base.py @@ -11,10 +11,24 @@ import copy import os +import warnings from collections.abc import Callable +from contextvars import ContextVar from functools import lru_cache from types import NoneType, UnionType -from typing import TYPE_CHECKING, Annotated, Any, Final, Literal, Union, cast, get_args, get_origin +from typing import ( + TYPE_CHECKING, + Annotated, + Any, + Final, + Generic, + Literal, + TypeVar, + Union, + cast, + get_args, + get_origin, +) from pydantic import ( AfterValidator, @@ -23,6 +37,7 @@ BeforeValidator, ConfigDict, GetCoreSchemaHandler, + RootModel, SerializationInfo, SerializerFunctionWrapHandler, StrictInt, @@ -509,7 +524,11 @@ def inherit_field(source: type[BaseModel], name: str, *, required: bool = False) # The marker belongs to an optional property (omit it, or supply a # value); a required field has no "omitted" to spell, and the tree-wide # invariant is that every marked field is optional. - field.metadata = [item for item in field.metadata if item is not _REFUSE_JSON_NULL] + field.metadata = [ + item + for item in field.metadata + if item is not _REFUSE_JSON_NULL and item is not OmitUnsetDefault + ] if field.exclude_if is NoExplicitNull.exclude_if: field.exclude_if = None return field @@ -525,6 +544,71 @@ def is_no_explicit_null(field: FieldInfo) -> bool: return field.exclude_if is NoExplicitNull.exclude_if +class _OmitUnsetDefault: + """Marks a schema-optional property that carries a schema ``default``. + + The generator writes the schema default into the field (``sandbox: bool = + False``), which is the right Python surface: the attribute reads as the + default when the property was omitted. On the wire, though, "omitted" and + "present with the default" are different bytes, and a digest or echo over + the document tells them apart (#1613). A field carrying this marker is + left off the default dump unless it is in ``model_fields_set`` -- the wire + set it, or the caller did. A schema-required property with a default + (and a generator-injected discriminator default) carries no marker and is + emitted either way. + + Carried in ``Annotated`` metadata; pydantic keeps unknown metadata on + ``FieldInfo.metadata``. One marker however the field is copied, like + :class:`_RefuseJsonNull`. + """ + + __slots__ = () + + def __repr__(self) -> str: + return "OmitUnsetDefault" + + def __copy__(self) -> _OmitUnsetDefault: + return self + + def __deepcopy__(self, memo: dict[int, Any]) -> _OmitUnsetDefault: + return self + + def __reduce__(self) -> str: + return "OmitUnsetDefault" + + def __eq__(self, other: object) -> bool: + return isinstance(other, _OmitUnsetDefault) + + def __hash__(self) -> int: + return hash(_OmitUnsetDefault) + + +#: The marker the generator appends to a schema-optional property with a +#: schema ``default`` (``scripts/generate_types.py`` marks it, +#: ``scripts/post_generate_fixes.py`` points it here). See +#: :class:`_OmitUnsetDefault`. +OmitUnsetDefault: Final = _OmitUnsetDefault() + + +def is_omit_unset_default(field: FieldInfo) -> bool: + """Whether ``field`` carries :data:`OmitUnsetDefault`.""" + return any(item is OmitUnsetDefault for item in field.metadata) + + +@lru_cache(maxsize=4096) +def omit_unset_default_fields(model: type[BaseModel]) -> frozenset[str]: + """The fields of ``model`` the default dump leaves off unless they were set.""" + return frozenset( + name for name, field in model.model_fields.items() if is_omit_unset_default(field) + ) + + +#: Set for the duration of a dump the caller asked for with +#: ``exclude_unset=False``: every field is emitted, defaults included, which is +#: the SDK 9 output. +_EMIT_UNSET_DEFAULTS: ContextVar[bool] = ContextVar("adcp_emit_unset_defaults", default=False) + + def _admits_none(annotation: Any) -> bool: """Whether ``annotation`` accepts ``None`` as a value.""" if annotation is Any or annotation is NoneType: @@ -682,6 +766,8 @@ def _finish_wire_dump(self, value: Any, info: SerializationInfo) -> Any: extra = self.__pydantic_extra__ if extra and info.exclude_none and not cls.model_fields: return _keep_opaque_nulls(value, extra, info) + if not info.exclude_unset and not _EMIT_UNSET_DEFAULTS.get(): + self._drop_unset_defaults(value, info) if info.exclude_none: self._put_back_explicit_nulls(value, info) # Bundled schemas can rename or inline the same wire shape. Match its @@ -690,10 +776,18 @@ def _finish_wire_dump(self, value: Any, info: SerializationInfo) -> Any: fields = cls.model_fields if {"subscriber_id", "url", "event_types", "product_payload_view"} <= fields.keys(): events = getattr(self, "event_types", ()) - if "product_payload_view" not in self.model_fields_set and not any( - str(getattr(e, "value", e)).startswith("product.") for e in events - ): - value.pop("product_payload_view", None) + product_event = any(str(getattr(e, "value", e)).startswith("product.") for e in events) + if "product_payload_view" not in self.model_fields_set: + if not product_event: + value.pop("product_payload_view", None) + elif "product_payload_view" not in value: + # A product subscription declares its payload view even when + # the caller left it at the default (the one schema default + # the SDK keeps emitting unset; see ``_drop_unset_defaults``). + view = getattr(self, "product_payload_view") + value["product_payload_view"] = ( + getattr(view, "value", view) if info.mode == "json" else view + ) return value # -- Explicit nulls (#1615) -------------------------------------------- @@ -754,6 +848,24 @@ def _unset_none_keys(self, data: Any) -> None: if name is not None: self.__pydantic_fields_set__.discard(name) + def _drop_unset_defaults(self, value: dict[str, Any], info: SerializationInfo) -> None: + """Leave off a schema-optional default the document (or caller) did not set.""" + cls = type(self) + marked = omit_unset_default_fields(cls) + if not marked: + return + # pydantic reports ``None`` here when the call passed no ``by_alias``. + by_alias: bool | None = info.by_alias + if by_alias is None: + by_alias = bool(cls.model_config.get("serialize_by_alias")) + fields_set = self.__pydantic_fields_set__ + for name in marked: + if name in fields_set: + continue + field = cls.model_fields[name] + key = (field.serialization_alias or field.alias or name) if by_alias else name + value.pop(key, None) + def _put_back_explicit_nulls(self, value: dict[str, Any], info: SerializationInfo) -> None: """Re-emit ``null`` for a set, null-admitting field that ``exclude_none`` dropped.""" cls = type(self) @@ -792,6 +904,7 @@ def model_dump(self, **kwargs: Any) -> dict[str, Any]: kwargs["exclude_none"] = True if "serialize_as_any" not in kwargs: kwargs["serialize_as_any"] = True + token = _EMIT_UNSET_DEFAULTS.set(kwargs.get("exclude_unset", None) is False) try: return super().model_dump(**kwargs) except (TypeError, PydanticSerializationError) as exc: @@ -799,38 +912,27 @@ def model_dump(self, **kwargs: Any) -> dict[str, Any]: raise _build_deferred_serializers(self, set()) return super().model_dump(**kwargs) + finally: + _EMIT_UNSET_DEFAULTS.reset(token) def model_dump_as_received(self, **kwargs: Any) -> dict[str, Any]: - """The document this model was parsed from, as JSON values. - - ``model_dump(mode="json")`` emits every field, so a property the wire - omitted comes back carrying the schema default the generator wrote - into the field (``sandbox: false``, ``timezone: "inventory_local"``). - A reader treats those as the same document; a - digest does not. ``core/canonical-proposal.json`` binds - ``terms_digest`` to the JCS form of ``commercial_terms`` *as declared*, - and a buyer recomputing it over a default dump gets a mismatch whenever - the seller left a defaulted property out (#1613). - - This dump leaves off every field the document did not set - (``exclude_unset``), keeps an explicit ``null`` on a property whose - schema admits one (``exclude_none=False``; a property the schema - refuses ``null`` on is left off by its own :data:`NoExplicitNull` - exclusion either way), and writes wire names (``by_alias``). With the - wire-preserving scalar types (:mod:`adcp.types._wire`), a schema-valid - document that reached the model round-trips byte-for-byte under RFC - 8785 -- except for a property the model never held, which - ``extra='ignore'`` dropped on input (#1614). - ``tests/conformance/test_wire_round_trip.py`` holds every digest- and - echo-covered schema to that, with no allowlist. - - For a model built in Python the result is exactly the fields the - caller set, in the deterministic forms the wire types document. + """Deprecated: ``model_dump(mode="json")`` is the document now. + + In SDK 9 the default dump emitted every field, so a schema default the + wire omitted came back, an admitted explicit ``null`` was dropped and + an aliased key was written under its Python name; this method was the + recipe that recovered the bytes (#1613, #1615). In SDK 10 the default + dump leaves an unset schema default off, re-emits a received ``null`` + and writes wire names, so the two are the same document. This alias + stays through 10.x and is removed in 11. """ + warnings.warn( + "model_dump_as_received() is deprecated: model_dump(mode='json') now dumps the " + "document as received. It will be removed in SDK 11.", + DeprecationWarning, + stacklevel=2, + ) kwargs.setdefault("mode", "json") - kwargs.setdefault("exclude_unset", True) - kwargs.setdefault("exclude_none", False) - kwargs.setdefault("by_alias", True) return self.model_dump(**kwargs) def model_dump_json(self, **kwargs: Any) -> str: @@ -838,6 +940,7 @@ def model_dump_json(self, **kwargs: Any) -> str: kwargs["exclude_none"] = True if "serialize_as_any" not in kwargs: kwargs["serialize_as_any"] = True + token = _EMIT_UNSET_DEFAULTS.set(kwargs.get("exclude_unset", None) is False) try: return super().model_dump_json(**kwargs) except (TypeError, PydanticSerializationError) as exc: @@ -845,6 +948,8 @@ def model_dump_json(self, **kwargs: Any) -> str: raise _build_deferred_serializers(self, set()) return super().model_dump_json(**kwargs) + finally: + _EMIT_UNSET_DEFAULTS.reset(token) def model_summary(self) -> str: """Human-readable summary for protocol responses. @@ -861,6 +966,54 @@ def model_summary(self) -> str: return f"{self.__class__.__name__} response" +_RootT = TypeVar("_RootT") + + +class AdCPRootModel(RootModel[_RootT], Generic[_RootT]): + """The ``RootModel`` base for generated composing roots. + + A generated schema root that is a union of object models, or a wrapper + another union discriminates against, keeps its ``RootModel`` + (``post_generate_fixes.unwrap_rootmodel_unions``). Pydantic's own + ``RootModel.model_dump`` emits every ``None`` of the arm it holds, so the + wrapper's dump differed from the arm's; this base gives it + :class:`AdCPBaseModel`'s defaults (``exclude_none``, ``serialize_as_any``) + so a root dumps the document its arm would (#1613). + """ + + def model_dump(self, **kwargs: Any) -> Any: + if "exclude_none" not in kwargs: + kwargs["exclude_none"] = True + if "serialize_as_any" not in kwargs: + kwargs["serialize_as_any"] = True + token = _EMIT_UNSET_DEFAULTS.set(kwargs.get("exclude_unset", None) is False) + try: + return super().model_dump(**kwargs) + except (TypeError, PydanticSerializationError) as exc: + if "MockValSer" not in str(exc): + raise + _build_deferred_serializers(self, set()) + return super().model_dump(**kwargs) + finally: + _EMIT_UNSET_DEFAULTS.reset(token) + + def model_dump_json(self, **kwargs: Any) -> str: + if "exclude_none" not in kwargs: + kwargs["exclude_none"] = True + if "serialize_as_any" not in kwargs: + kwargs["serialize_as_any"] = True + token = _EMIT_UNSET_DEFAULTS.set(kwargs.get("exclude_unset", None) is False) + try: + return super().model_dump_json(**kwargs) + except (TypeError, PydanticSerializationError) as exc: + if "MockValSer" not in str(exc): + raise + _build_deferred_serializers(self, set()) + return super().model_dump_json(**kwargs) + finally: + _EMIT_UNSET_DEFAULTS.reset(token) + + class RegistryBaseModel(BaseModel): """Base model for registry API types. diff --git a/src/adcp/types/domains/a2ui/si_catalog.py b/src/adcp/types/domains/a2ui/si_catalog.py index 2396777c8..377ff9d6a 100644 --- a/src/adcp/types/domains/a2ui/si_catalog.py +++ b/src/adcp/types/domains/a2ui/si_catalog.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: a2ui/si_catalog.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl from pydantic import Field, StrictBool from . import bound_value @@ -29,7 +29,7 @@ class Component(StrEnum): class SiComponentCatalog(AdCPBaseModel): catalogId: Annotated[ - Literal['si-standard'], Field(description='SI standard component catalog') + Literal['si-standard'], Field(description='SI standard component catalog'), OmitUnsetDefault ] = 'si-standard' components: Annotated[ list[Component] | None, Field(description='Available component types'), NoExplicitNull @@ -45,7 +45,7 @@ class Variant(StrEnum): class Text(AdCPBaseModel): text: Annotated[bound_value.A2UiBoundValue, Field(description='Text content to display')] - variant: Annotated[Variant | None, NoExplicitNull] = Variant.body + variant: Annotated[Variant | None, NoExplicitNull, OmitUnsetDefault] = Variant.body class Action(AdCPBaseModel): @@ -66,7 +66,7 @@ class Variant4(StrEnum): class Button(AdCPBaseModel): label: Annotated[bound_value.A2UiBoundValue, Field(description='Button label text')] action: Annotated[Action, Field(description='Action to trigger on click')] - variant: Annotated[Variant4 | None, NoExplicitNull] = Variant4.primary + variant: Annotated[Variant4 | None, NoExplicitNull, OmitUnsetDefault] = Variant4.primary disabled: Annotated[bound_value.A2UiBoundValue | None, NoExplicitNull] = None @@ -74,7 +74,10 @@ class Link(AdCPBaseModel): label: Annotated[bound_value.A2UiBoundValue, Field(description='Link text')] url: Annotated[bound_value.A2UiBoundValue, Field(description='URL to navigate to')] external: Annotated[ - StrictBool | None, Field(description='Opens in new tab if true'), NoExplicitNull + StrictBool | None, + Field(description='Opens in new tab if true'), + NoExplicitNull, + OmitUnsetDefault, ] = True @@ -153,7 +156,7 @@ class List(AdCPBaseModel): Field(description='Message to show when list is empty'), NoExplicitNull, ] = None - layout: Annotated[Layout | None, NoExplicitNull] = Layout.vertical + layout: Annotated[Layout | None, NoExplicitNull, OmitUnsetDefault] = Layout.vertical class Align(StrEnum): @@ -173,15 +176,19 @@ class Justify(StrEnum): class Row(AdCPBaseModel): children: Annotated[list[str], Field(description='Child component IDs')] - gap: Annotated[str | None, Field(description='Gap between children'), NoExplicitNull] = '8px' - align: Annotated[Align | None, NoExplicitNull] = Align.center - justify: Annotated[Justify | None, NoExplicitNull] = Justify.start + gap: Annotated[ + str | None, Field(description='Gap between children'), NoExplicitNull, OmitUnsetDefault + ] = '8px' + align: Annotated[Align | None, NoExplicitNull, OmitUnsetDefault] = Align.center + justify: Annotated[Justify | None, NoExplicitNull, OmitUnsetDefault] = Justify.start class Column(AdCPBaseModel): children: Annotated[list[str], Field(description='Child component IDs')] - gap: Annotated[str | None, Field(description='Gap between children'), NoExplicitNull] = '8px' - align: Annotated[Align | None, NoExplicitNull] = Align.stretch + gap: Annotated[ + str | None, Field(description='Gap between children'), NoExplicitNull, OmitUnsetDefault + ] = '8px' + align: Annotated[Align | None, NoExplicitNull, OmitUnsetDefault] = Align.stretch class Type(StrEnum): @@ -198,7 +205,10 @@ class IntegrationAction(AdCPBaseModel): NoExplicitNull, ] = None highlighted: Annotated[ - StrictBool | None, Field(description='Visually emphasize this action'), NoExplicitNull + StrictBool | None, + Field(description='Visually emphasize this action'), + NoExplicitNull, + OmitUnsetDefault, ] = False diff --git a/src/adcp/types/domains/a2ui/surface.py b/src/adcp/types/domains/a2ui/surface.py index 3d33108aa..6ab60d36c 100644 --- a/src/adcp/types/domains/a2ui/surface.py +++ b/src/adcp/types/domains/a2ui/surface.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: a2ui/surface.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field from . import component @@ -18,7 +18,10 @@ class A2UiSurface(AdCPBaseModel): ) surfaceId: Annotated[str, Field(description='Unique identifier for this surface')] catalogId: Annotated[ - str | None, Field(description='Component catalog to use for rendering'), NoExplicitNull + str | None, + Field(description='Component catalog to use for rendering'), + NoExplicitNull, + OmitUnsetDefault, ] = 'standard' components: Annotated[ list[component.A2UiComponent], diff --git a/src/adcp/types/domains/account/list_account_changes_request.py b/src/adcp/types/domains/account/list_account_changes_request.py index 983bf97e5..2e568b858 100644 --- a/src/adcp/types/domains/account/list_account_changes_request.py +++ b/src/adcp/types/domains/account/list_account_changes_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: account/list_account_changes_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field @@ -73,6 +73,7 @@ class ListAccountChangesRequest(AdcpRequest, AdcpVersionEnvelope): description='Initial position when cursor is absent. Mutually exclusive with cursor; sellers enforce this semantic rule at runtime so the emitted MCP input schema can remain a plain root object. earliest intentionally begins at the oldest retained change. latest returns a checkpoint at the current seller-ingestion high-water and is used before a race-free snapshot bootstrap.' ), NoExplicitNull, + OmitUnsetDefault, ] = StartingPosition.earliest resource_types: Annotated[ list[ResourceType] | None, @@ -91,6 +92,7 @@ class ListAccountChangesRequest(AdcpRequest, AdcpVersionEnvelope): le=100, ), NoExplicitNull, + OmitUnsetDefault, ] = 50 context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/account/list_accounts_request.py b/src/adcp/types/domains/account/list_accounts_request.py index 5ad26c8aa..a6910f95b 100644 --- a/src/adcp/types/domains/account/list_accounts_request.py +++ b/src/adcp/types/domains/account/list_accounts_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: account/list_accounts_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import account_ref @@ -58,6 +58,7 @@ class ListAccountsRequest(AdcpRequest, AdcpVersionEnvelope): description='When true, request recent webhook delivery attempts for each returned account in account.webhook_activity[]. Sellers MAY omit webhook_activity if they do not expose this debug log; when present, three-state semantics match the shared webhook_activity[] contract.' ), NoExplicitNull, + OmitUnsetDefault, ] = False webhook_activity_limit: Annotated[ SchemaInt | None, @@ -67,6 +68,7 @@ class ListAccountsRequest(AdcpRequest, AdcpVersionEnvelope): le=200, ), NoExplicitNull, + OmitUnsetDefault, ] = 50 context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/account/sync_accounts_request.py b/src/adcp/types/domains/account/sync_accounts_request.py index e8d7ef4fb..4b437d88a 100644 --- a/src/adcp/types/domains/account/sync_accounts_request.py +++ b/src/adcp/types/domains/account/sync_accounts_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: account/sync_accounts_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import account_ref, brand_ref, business_entity @@ -294,6 +294,7 @@ class SyncAccountsRequest(AdcpRequest, AdcpVersionEnvelope): description='When true, accounts previously synced by this agent but not included in this request will be deactivated. Scoped to the authenticated agent — does not affect accounts managed by other agents. Use with caution.' ), NoExplicitNull, + OmitUnsetDefault, ] = False dry_run: Annotated[ StrictBool | None, @@ -301,6 +302,7 @@ class SyncAccountsRequest(AdcpRequest, AdcpVersionEnvelope): description='When true, preview what would change without applying. Returns what would be created/updated/deactivated.' ), NoExplicitNull, + OmitUnsetDefault, ] = False push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, diff --git a/src/adcp/types/domains/adagents.py b/src/adcp/types/domains/adagents.py index cccee6ce2..0f1bd74de 100644 --- a/src/adcp/types/domains/adagents.py +++ b/src/adcp/types/domains/adagents.py @@ -1,15 +1,15 @@ # generated by datamodel-codegen: # filename: adagents.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl, WireDatetime, WireEmail +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl, WireDatetime, WireEmail from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel, StrictBool, model_validator +from pydantic import ConfigDict, Field, StrictBool, model_validator from .core import ( collection, @@ -832,6 +832,7 @@ class AdcpAgentsAuthorization27(AdCPBaseModel): Field( description='Marks a catalog payload intended for the fixed AgenticAdvertising.org community-format registry. This self-description is not provenance: consumers accept reference_renderer only when the document was obtained from the configured, reviewed community-registry origin.' ), + OmitUnsetDefault, ] = None properties: Annotated[ list[property.Property] | None, @@ -956,6 +957,7 @@ class AdcpAgentsAuthorization29(AdcpAgentsAuthorization22, AdcpAgentsAuthorizati Field( description='Marks a catalog payload intended for the fixed AgenticAdvertising.org community-format registry. This self-description is not provenance: consumers accept reference_renderer only when the document was obtained from the configured, reviewed community-registry origin.' ), + OmitUnsetDefault, ] = None properties: Annotated[ list[property.Property] | None, @@ -1072,7 +1074,7 @@ class AdcpAgentsAuthorization213(AdcpAgentsAuthorization27): class AdcpAgentsAuthorization2( - RootModel[ + AdCPRootModel[ AdcpAgentsAuthorization28 | AdcpAgentsAuthorization29 | AdcpAgentsAuthorization210 @@ -1446,7 +1448,7 @@ def __getattr__(self, name: str) -> Any: -class AdcpAgentsAuthorization(RootModel[AdcpAgentsAuthorization1 | AdcpAgentsAuthorization2]): +class AdcpAgentsAuthorization(AdCPRootModel[AdcpAgentsAuthorization1 | AdcpAgentsAuthorization2]): root: Annotated[ AdcpAgentsAuthorization1 | AdcpAgentsAuthorization2, Field( diff --git a/src/adcp/types/domains/brand/get_rights_request.py b/src/adcp/types/domains/brand/get_rights_request.py index abe51f1c5..137a97c89 100644 --- a/src/adcp/types/domains/brand/get_rights_request.py +++ b/src/adcp/types/domains/brand/get_rights_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: brand/get_rights_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool @@ -76,6 +76,7 @@ class GetRightsRequest(AdcpRequest, AdcpVersionEnvelope): description='Include filtered-out results in the excluded array with reasons. Defaults to false.' ), NoExplicitNull, + OmitUnsetDefault, ] = False pagination: Annotated[ pagination_request.PaginationRequest | None, diff --git a/src/adcp/types/domains/brand/verify_brand_claims_response.py b/src/adcp/types/domains/brand/verify_brand_claims_response.py index f81beb053..53c522a07 100644 --- a/src/adcp/types/domains/brand/verify_brand_claims_response.py +++ b/src/adcp/types/domains/brand/verify_brand_claims_response.py @@ -3,13 +3,13 @@ from __future__ import annotations -from adcp.types.base import AdcpResponse +from adcp.types.base import AdCPRootModel, AdcpResponse from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal from adcp.types.base import AdCPBaseModel, WireAnyUrl -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..core import context as context_1 from ..core import error as error_1 @@ -59,7 +59,7 @@ class ResultEntry2(AdCPBaseModel): error: error_1.Error -class ResultEntry(RootModel[ResultEntry1 | ResultEntry2]): +class ResultEntry(AdCPRootModel[ResultEntry1 | ResultEntry2]): root: Annotated[ ResultEntry1 | ResultEntry2, Field( diff --git a/src/adcp/types/domains/brand_discovery.py b/src/adcp/types/domains/brand_discovery.py index 45a7b5f86..cd1d96890 100644 --- a/src/adcp/types/domains/brand_discovery.py +++ b/src/adcp/types/domains/brand_discovery.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: _brand_discovery.json -# timestamp: 2026-10-26T23:55:00+00:00 +# timestamp: 2026-10-28T15:43:53+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Dict, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl, WireDatetime, WireEmail +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull, SchemaInt, WireAnyUrl, WireDatetime, WireEmail from adcp.types._scalar import ScalarFloat, ScalarInt, ScalarStr from pydantic import ( ConfigDict, @@ -186,11 +186,11 @@ class PortfolioEntry(AdCPBaseModel): ] = None -class LocalizedName(RootModel[dict[str, str]]): +class LocalizedName(AdCPRootModel[dict[str, str]]): root: Annotated[dict[str, str], Field(max_length=1, min_length=1)] -class LocalizedScalar1(RootModel[list[LocalizedName]]): +class LocalizedScalar1(AdCPRootModel[list[LocalizedName]]): root: Annotated[ list[LocalizedName], Field( @@ -200,7 +200,7 @@ class LocalizedScalar1(RootModel[list[LocalizedName]]): ] -class LocalizedScalar(RootModel[str | LocalizedScalar1]): +class LocalizedScalar(AdCPRootModel[str | LocalizedScalar1]): root: Annotated[ str | LocalizedScalar1, Field( @@ -215,11 +215,11 @@ def __getattr__(self, name: str) -> Any: -class LocalizedStringListEntry(RootModel[dict[str, list[str]]]): +class LocalizedStringListEntry(AdCPRootModel[dict[str, list[str]]]): root: Annotated[dict[str, list[str]], Field(max_length=1, min_length=1)] -class LocalizedStringList1(RootModel[list[LocalizedStringListEntry]]): +class LocalizedStringList1(AdCPRootModel[list[LocalizedStringListEntry]]): root: Annotated[ list[LocalizedStringListEntry], Field( @@ -229,7 +229,7 @@ class LocalizedStringList1(RootModel[list[LocalizedStringListEntry]]): ] -class LocalizedStringList(RootModel[list[str] | LocalizedStringList1]): +class LocalizedStringList(AdCPRootModel[list[str] | LocalizedStringList1]): root: Annotated[ list[str] | LocalizedStringList1, Field( @@ -301,11 +301,11 @@ class ColorValue2Item(ColorValue1): pass -class ColorValue2(RootModel[list[ColorValue2Item]]): +class ColorValue2(AdCPRootModel[list[ColorValue2Item]]): root: Annotated[list[ColorValue2Item], Field(min_length=1)] -class ColorValue(RootModel[ColorValue1 | ColorValue2]): +class ColorValue(AdCPRootModel[ColorValue1 | ColorValue2]): root: ColorValue1 | ColorValue2 def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" @@ -394,7 +394,7 @@ class ColorRef3(AdCPBaseModel): ] -class ColorRef(RootModel[ColorRef1 | ColorRef2 | ColorRef3]): +class ColorRef(AdCPRootModel[ColorRef1 | ColorRef2 | ColorRef3]): root: Annotated[ ColorRef1 | ColorRef2 | ColorRef3, Field( @@ -501,7 +501,7 @@ class FontRole1(AdCPBaseModel): ] = None -class FontRole(RootModel[str | FontRole1]): +class FontRole(AdCPRootModel[str | FontRole1]): root: Annotated[ str | FontRole1, Field( @@ -662,7 +662,7 @@ class ProhibitedTerm(ScalarStr): _constraints = {'min_length': 1} -class Tagline(RootModel[list[LocalizedName]]): +class Tagline(AdCPRootModel[list[LocalizedName]]): root: Annotated[ list[LocalizedName], Field(description='Localized taglines with BCP 47 locale codes', min_length=1), @@ -2314,7 +2314,7 @@ class BrandAgentEntry(AdCPBaseModel): ] = None -class Agents(RootModel[list[BrandAgentEntry]]): +class Agents(AdCPRootModel[list[BrandAgentEntry]]): root: Annotated[ list[BrandAgentEntry], Field( diff --git a/src/adcp/types/domains/bundled/protocol/get_adcp_capabilities_response.py b/src/adcp/types/domains/bundled/protocol/get_adcp_capabilities_response.py index 60a746832..72d87798e 100644 --- a/src/adcp/types/domains/bundled/protocol/get_adcp_capabilities_response.py +++ b/src/adcp/types/domains/bundled/protocol/get_adcp_capabilities_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: bundled/protocol/get_adcp_capabilities_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -11,7 +11,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Dict, Literal -from adcp.types.base import AdCPBaseModel, GeoPlaceSystemKey, NoExplicitNull, SchemaInt, WireAnyUrl, WireDatetime, WireEmail +from adcp.types.base import AdCPBaseModel, AdCPRootModel, GeoPlaceSystemKey, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl, WireDatetime, WireEmail from adcp.types._scalar import ScalarFloat, ScalarInt, ScalarStr from pydantic import ( ConfigDict, @@ -335,6 +335,7 @@ class Idempotency(AdCPBaseModel): description="When true, the seller derives `account_id` via an HKDF-based one-way transform of the buyer's natural account key rather than echoing the natural key on the wire. Buyers MUST NOT attempt to invert the opaque id and MUST treat it as a blind handle scoped to this seller. Absent or false, callers should assume `account_id` is the natural key (or a server-assigned but non-opaque id). This flag does not change the wire shape, but it DOES change buyer behavior — buyers MUST NOT cache, log, or treat `account_id` as a natural-key analog when this flag is true. Migration note for sellers already returning an opaque id without this flag: set it to true at the next capabilities refresh so buyers stop inferring natural-key semantics; until set, new-buyer replay/retry logic will misclassify these ids as natural keys." ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -419,7 +420,7 @@ class ReportingDestinationOffering(AdCPBaseModel): class CallerEventType( - RootModel[ + AdCPRootModel[ Literal[ 'scheduled', 'final', @@ -895,7 +896,7 @@ class Issuer2(AdCPBaseModel): ] = None -class CredentialOrigin(RootModel[WireAnyUrl]): +class CredentialOrigin(AdCPRootModel[WireAnyUrl]): root: WireAnyUrl @@ -1038,6 +1039,7 @@ class Notifications6(AdCPBaseModel): description='Whether `list_accounts` can return recent delivery attempts in `accounts[].webhook_activity[]` when the buyer requests `include_webhook_activity: true`.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -1231,6 +1233,7 @@ class ProposalRefinement(AdCPBaseModel): description="Whether the seller accepts action extend_hold on refine_proposals (feature id media_buy.hold_extension). This is the agent-level gate for the action: buyers MUST NOT send extend_hold unless this is true, and a seller that does not declare it fails the unknown action with VALIDATION_ERROR. extend_hold applies only to committed proposals that hold inventory; against a committed proposal with no hold (an entry whose effective policy is terms-only: hold_policy.available false, or no hold_policy at a seller declaring hold_semantics terms_only) the seller fails the request at task level with INVALID_STATE. When the committed proposal carries hold_policy, extendable false or absent (it defaults to false) means the seller declines with hold_unavailable and buyers SHOULD NOT send extend_hold; the successor's expires_at MUST NOT exceed the original hold_placed_at plus max_total_duration, which successors carry forward unchanged across the whole extension chain; a request the seller cannot extend at all returns hold_unavailable. Absent hold_policy (a seller that does not list media_buy.hold_policy), limits remain seller policy expressed through hold_unavailable." ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -1279,6 +1282,7 @@ class OpenOpportunities(AdCPBaseModel): description='Whether the seller implements list_proposals. Required true when later_proposals.supported is true, because list_proposals is the repair read for proposal.created. Sellers declaring media_buy.seller_offers also require it, as the repair read for proposal.offered, the buy-scoped sibling notification for seller offers against accepted media buys. Seller offers are distinct from later proposals: later_proposals covers new_media_buy drafts under buyer consent, and media_buy_update proposals are out of scope for it.' ), NoExplicitNull, + OmitUnsetDefault, ] = False keep_open_after_purchase: Annotated[ StrictBool | None, @@ -1286,6 +1290,7 @@ class OpenOpportunities(AdCPBaseModel): description='Whether accept_proposal and buy_products accept an explicit opportunity.status open.' ), NoExplicitNull, + OmitUnsetDefault, ] = False later_proposals: Annotated[ LaterProposals | None, @@ -1306,6 +1311,7 @@ class PerformanceFeedback(AdCPBaseModel): description='Whether provide_performance_feedback responses include an honest application_status of accepted, applied, or not_applied. accepted is explicitly not an application claim. Sellers that cannot determine optimizer disposition omit this claim or set it false.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -1365,10 +1371,10 @@ class ReportingProfile(AdCPBaseModel): ), NoExplicitNull, ] = None - canonicalization_contract_version: Literal['1.0'] = '1.0' - canonicalization_media_type: Literal['application/vnd.adcp.reporting-canonicalization+json'] = ( - 'application/vnd.adcp.reporting-canonicalization+json' - ) + canonicalization_contract_version: Annotated[Literal['1.0'], OmitUnsetDefault] = '1.0' + canonicalization_media_type: Annotated[ + Literal['application/vnd.adcp.reporting-canonicalization+json'], OmitUnsetDefault + ] = 'application/vnd.adcp.reporting-canonicalization+json' canonicalization_uri: Annotated[ WireAnyUrl | None, Field( @@ -1622,6 +1628,7 @@ class RelationshipNotifications(AdCPBaseModel): description='Whether the declared repair tasks can return recent delivery attempts for these account-level invalidations when include_webhook_activity is true.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -2008,7 +2015,7 @@ class Countries8(StrEnum): ward = 'ward' -class Countries9(RootModel[WireAnyUrl]): +class Countries9(AdCPRootModel[WireAnyUrl]): root: Annotated[ WireAnyUrl, Field( @@ -2119,7 +2126,7 @@ class GeoPlaces1(StrEnum): microsoft_ads = 'microsoft_ads' -class GeoPlaces2(RootModel[WireAnyUrl]): +class GeoPlaces2(AdCPRootModel[WireAnyUrl]): root: Annotated[ WireAnyUrl, Field( @@ -2485,6 +2492,7 @@ class Signals(AdCPBaseModel): description="Whether `data_provider_domains` enumerates every data provider this agent resells. Absent or `true` means the list is complete as served. `false` means the agent resells more providers than it published — because the real set exceeds the advisory item bound or the per-field byte budget — and the list is a sample. This flag is advisory and affects prefetch planning only, never an authorization decision: buyers MUST NOT treat `data_provider_domains` as an allowlist whether it is marked complete or not, and MUST resolve each signal's provider through that provider's own adagents.json. Meaningless without `data_provider_domains`, which the sibling `dependencies` rule requires." ), NoExplicitNull, + OmitUnsetDefault, ] = True discovery_modes: Annotated[ list[DiscoveryMode] | None, @@ -2493,6 +2501,7 @@ class Signals(AdCPBaseModel): min_length=1, ), NoExplicitNull, + OmitUnsetDefault, ] = [DiscoveryMode.brief] features: Annotated[ Features | None, Field(description='Optional signals features supported'), NoExplicitNull @@ -2726,6 +2735,7 @@ class Modalities(AdCPBaseModel): StrictBool | None, Field(description='Pure text exchange - the baseline modality'), NoExplicitNull, + OmitUnsetDefault, ] = True voice: Annotated[ StrictBool | Voice | None, @@ -2783,7 +2793,10 @@ class A2ui(AdCPBaseModel): extra='allow', ) supported: Annotated[ - StrictBool | None, Field(description='Supports A2UI surface rendering'), NoExplicitNull + StrictBool | None, + Field(description='Supports A2UI surface rendering'), + NoExplicitNull, + OmitUnsetDefault, ] = False catalogs: Annotated[ list[str] | None, @@ -2812,6 +2825,7 @@ class Capabilities(AdCPBaseModel): StrictBool | None, Field(description='Supports MCP Apps for rendering A2UI surfaces in iframes'), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -2863,6 +2877,7 @@ class Brand(AdCPBaseModel): description='Supports get_rights and acquire_rights for rights discovery and clearance' ), NoExplicitNull, + OmitUnsetDefault, ] = False right_types: Annotated[ list[RightType] | None, @@ -2939,6 +2954,7 @@ class Multiplicity(AdCPBaseModel): description='When true, build_creative accepts max_creatives (one distinct creative per catalog item).' ), NoExplicitNull, + OmitUnsetDefault, ] = False max_creatives_limit: Annotated[ SchemaInt | None, @@ -2951,6 +2967,7 @@ class Multiplicity(AdCPBaseModel): description='Experimental (x-status: experimental) — agents setting this true MUST also list `creative.signal_fanout` in `experimental_features`; the surface MAY change between 3.x releases with notice (see docs/reference/experimental-status). When true, build_creative accepts signal_conditions[] (one distinct creative group per signal condition, keep-all). Mirrors supports_catalog_fanout.' ), NoExplicitNull, + OmitUnsetDefault, ] = False max_signal_conditions_limit: Annotated[ SchemaInt | None, @@ -2966,6 +2983,7 @@ class Multiplicity(AdCPBaseModel): description='When true, build_creative accepts max_variants > 1 / variant_axis (alternatives per creative).' ), NoExplicitNull, + OmitUnsetDefault, ] = False max_variants_limit: Annotated[ SchemaInt | None, @@ -3255,6 +3273,7 @@ class RequestSigning(AdCPBaseModel): validate_default=True, ), NoExplicitNull, + OmitUnsetDefault, ] = [] warn_for: Annotated[ list[Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')]] | None, @@ -3263,6 +3282,7 @@ class RequestSigning(AdCPBaseModel): validate_default=True, ), NoExplicitNull, + OmitUnsetDefault, ] = [] supported_for: Annotated[ list[Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')]] | None, @@ -3278,6 +3298,7 @@ class RequestSigning(AdCPBaseModel): validate_default=True, ), NoExplicitNull, + OmitUnsetDefault, ] = [] protocol_methods_warn_for: Annotated[ list[ProtocolMethodsWarnForItem] | None, @@ -3286,6 +3307,7 @@ class RequestSigning(AdCPBaseModel): validate_default=True, ), NoExplicitNull, + OmitUnsetDefault, ] = [] protocol_methods_required_for: Annotated[ list[ProtocolMethodsRequiredForItem] | None, @@ -3294,6 +3316,7 @@ class RequestSigning(AdCPBaseModel): validate_default=True, ), NoExplicitNull, + OmitUnsetDefault, ] = [] operation_sources: Annotated[ list[OperationSource] | None, @@ -3310,6 +3333,7 @@ class RequestSigning(AdCPBaseModel): min_length=1, ), NoExplicitNull, + OmitUnsetDefault, ] = [Profile.adcp_request_signing_v1] @@ -3356,6 +3380,7 @@ class CompromiseNotification(AdCPBaseModel): StrictBool | None, Field(description='Whether this agent emits `identity.compromise_notification` events.'), NoExplicitNull, + OmitUnsetDefault, ] = False accepts: Annotated[ StrictBool | None, @@ -3363,6 +3388,7 @@ class CompromiseNotification(AdCPBaseModel): description='Whether this agent subscribes to `identity.compromise_notification` events from counterparties it verifies signatures from.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -3383,6 +3409,7 @@ class Identity(AdCPBaseModel): description="When true, this multi-principal operator scopes signing keys per-principal so a single principal's key compromise does not silently re-scope across principals served by the same operator. `kid` values remain opaque to verifiers per RFC 7517; any operator-side naming convention (e.g., `{operator}:{principal}:{key_version}`) is internal bookkeeping and MUST NOT be parsed by verifiers. See docs/building/understanding/security-model.mdx." ), NoExplicitNull, + OmitUnsetDefault, ] = False key_origins: Annotated[ KeyOrigins | None, @@ -3540,6 +3567,7 @@ class Measurement(AdCPBaseModel): description="Whether this measurement agent produces compact provide_performance_feedback assertions for a buyer-controlled orchestrator gateway. In the first experimental tier the provider reads delivery through the gateway's get_media_buy_delivery task and returns assertions through its provide_performance_feedback task. The gateway decides what to forward to each seller." ), NoExplicitNull, + OmitUnsetDefault, ] = False metrics: Annotated[ list[Metric], @@ -5104,6 +5132,7 @@ class Account(AdCPBaseModel): description="Whether the seller requires operator-level credentials. This declares who must authenticate; it does not by itself declare whether OAuth is used, whether list_accounts is exposed, or which sync_accounts modes are supported. When true, operators authenticate independently with the seller and account-scoped calls use seller/storefront-assigned account_id values because the seller or upstream platform owns the canonical account namespace. If a credential may access more than one account, the seller MUST expose list_accounts and buyers MUST resolve an explicit account_id before the first account-scoped request. If a credential is bound to exactly one account, the seller SHOULD expose list_accounts returning that singleton; a seller MAY omit list_accounts only when it provides the same explicit account_id through another declared path or out-of-band onboarding. When false (default, buyer-declared accounts), the seller trusts the agent's identity claims and account-scoped calls use the advertiser natural key: brand + operator + optional operator_unit, fixed currency, optional buyer-selected account timezone, and sandbox. operator_unit.id is owned by the operator and is distinct from the seller's account_id. The seller normally provisions through sync_accounts, but MAY lazily provision on the first provisioning task (one that commits spend or creates account-owned resources, such as create_media_buy) when billing and other required settings are unambiguous from capabilities or onboarding defaults. Discovery and negotiation tasks MUST NOT provision; on them, a natural key that is not yet provisioned returns ACCOUNT_NOT_FOUND unless a lazy-provisioning seller answers for the account it would create. See /docs/accounts/overview#account-references-before-provisioning. A lazy-provisioning seller MUST keep accepting the natural key and MUST expose list_accounts for recovery; if buyer input is needed before use, the seller MUST expose sync_accounts." ), NoExplicitNull, + OmitUnsetDefault, ] = False authorization_endpoint: Annotated[ WireAnyUrl | None, @@ -5118,6 +5147,7 @@ class Account(AdCPBaseModel): description="Whether the seller-assigned account_id is a durable AccountRef for every account the caller can operate, including buyer-declared accounts. When true, the seller MUST return account_id on every non-failed sync_accounts provisioning result, MUST accept { account_id } wherever an AccountRef is accepted, and MUST NOT change an account's account_id for the life of the account, including across identity reconciliation. A buyer-declared seller that declares true still accepts the account's current natural key throughout 3.x. Buyers holding an account_id from a seller that declares true SHOULD send it in place of the natural key. When false or absent, buyers follow the account model declared by require_operator_auth and MUST NOT assume a buyer-declared seller accepts its returned account_id. Sellers whose accounts already use account_id references (require_operator_auth: true) SHOULD declare true when their ids meet these rules. Using the natural key as the reference after provisioning is deprecated as of 3.3 and planned for removal in 4.0, so sellers SHOULD declare true during 3.x to give buyers a migration path." ), NoExplicitNull, + OmitUnsetDefault, ] = False supported_billing: Annotated[ list[BillingParty], @@ -5148,6 +5178,7 @@ class Account(AdCPBaseModel): description='Whether an account reference is required for get_products. When true, the buyer must establish an account before browsing products. When false (default), the buyer can browse products without an account — useful for price comparison and discovery before committing to a seller. Before an account is provisioned, the buyer omits account rather than sending a natural key that does not resolve yet. See /docs/accounts/overview#account-references-before-provisioning.' ), NoExplicitNull, + OmitUnsetDefault, ] = False account_financials: Annotated[ StrictBool | None, @@ -5155,6 +5186,7 @@ class Account(AdCPBaseModel): description='Whether this seller exposes the `get_account_financials` task for querying account-level financial status (spend, credit, invoices). Acts as a **pre-call discriminator** — buyers MUST consult this field before issuing `get_account_financials`; when `false` (or absent), sellers MAY reject the call with an `UNSUPPORTED_FEATURE` / `OPERATION_NOT_SUPPORTED` error. Companion pattern to `creative.bills_through_adcp` (issue #2881) — both fields let buyers gate optional capability calls on a single declared boolean rather than probing for support. Only applicable to operator-billed accounts; sellers using buyer-billed flows omit or set to `false`.' ), NoExplicitNull, + OmitUnsetDefault, ] = False notifications: Annotated[ Notifications6 | Notifications7 | None, @@ -5183,6 +5215,7 @@ class Account(AdCPBaseModel): description='Whether this seller supports sandbox accounts for testing. Buyer-declared accounts use sandbox: true in sync_accounts or, for an unambiguous lazy-provisioning seller, in the natural-key account reference. Sellers with account_id namespaces expose sandbox accounts as pre-existing test accounts through list_accounts or supply them out-of-band. Requests using a sandbox account perform no real platform calls or spend.' ), NoExplicitNull, + OmitUnsetDefault, ] = False agent_onboarding: Annotated[ AgentOnboarding | None, @@ -5353,6 +5386,7 @@ class ReportingDelivery(AdCPBaseModel): Field( description='Explicit adoption declaration for the proper-name AdCP 3.2 Reliable Reporting contract. Presence, together with supported: true and the media_buy.reporting_delivery experimental feature gate, is the affirmative machine-readable answer. Absence denotes the earlier experimental managed-reporting shape.' ), + OmitUnsetDefault, ] = None managed_delivery: Annotated[ StrictBool | None, @@ -5360,6 +5394,7 @@ class ReportingDelivery(AdCPBaseModel): description='Tier flag: this seller supports managed file, dataset-share, or warehouse delivery. Offerings whose method names a delivery pattern require this tier. When false or absent, every offering is API-delivered and Core-only.' ), NoExplicitNull, + OmitUnsetDefault, ] = None reconciled_billing: Annotated[ StrictBool | None, @@ -5367,6 +5402,7 @@ class ReportingDelivery(AdCPBaseModel): description='Tier flag: this seller supports canonical-digest verification and authenticated consumer receipts for both report materializations and post-official adjustments through receipt_task. Offerings with reconciliation_mode consumer_receipt and billing-grade canonicalization require this tier.' ), NoExplicitNull, + OmitUnsetDefault, ] = None configuration_task: Literal['sync_accounts'] | None = None status_task: Literal['get_reporting_status'] | None = None @@ -5375,36 +5411,42 @@ class ReportingDelivery(AdCPBaseModel): Field( description='Opt-in consumer-status loop during the published migration window, becoming required Core in the next eligible minor after that window. Buyers call this seller-hosted task to record whether each expected reporting period was received, missing, or unreadable. Buyers expose no reverse endpoint, and the status is not a billing receipt.' ), + OmitUnsetDefault, ] = None revision_content_task: Annotated[ Literal['get_media_buy_delivery'] | None, Field( description='Reliable Reporting exact-content read: callers select reporting_revision_id and receive immutable revision metadata plus authoritative canonical reporting_rows.' ), + OmitUnsetDefault, ] = None receipt_task: Annotated[ Literal['sync_reporting_receipts'] | None, Field( description='Required when reconciled_billing is true: the task consumers call to submit and read back authenticated revision and adjustment receipts.' ), + OmitUnsetDefault, ] = None readiness_notification: Annotated[ Literal['reporting.delivery_ready'] | None, Field( description='Optional managed-delivery-only positive-readiness doorbell. It names a materialization at a destination, so Core sellers MUST omit it.' ), + OmitUnsetDefault, ] = None status_notification: Annotated[ Literal['reporting.status_changed'] | None, Field( description='Optional tier-independent invalidation doorbell for health transitions in either direction, including clock-driven waiting-to-delayed and delayed-to-action_required. Valid for Core: it names no destination. Polling status_task remains the authoritative recovery path whether or not this is offered.' ), + OmitUnsetDefault, ] = None ledger_notification: Annotated[ Literal['reporting.ledger_changed'] | None, Field( description='Optional tier-independent invalidation for every newly committed revision or post-official adjustment, even when health does not change. Receivers repair through get_reporting_status changes_after; polling remains authoritative.' ), + OmitUnsetDefault, ] = None offerings: Annotated[ list[Offering], @@ -5457,7 +5499,9 @@ class ReportingDelivery(AdCPBaseModel): ), NoExplicitNull, ] = None - supports_webhook_activity: Annotated[StrictBool | None, NoExplicitNull] = None + supports_webhook_activity: Annotated[StrictBool | None, NoExplicitNull, OmitUnsetDefault] = ( + None + ) authorization_revocation_seconds: Annotated[ SchemaInt | None, Field( @@ -5589,6 +5633,7 @@ class CreativeSpecs(AdCPBaseModel): description="Level of VAST asset validation the seller performs at sync_creatives (including dry_run): 'structural' checks manifest shape and format requirements only and never inspects the VAST document; 'document' additionally parses the VAST document and can return VAST_PARSE_FAILED / VAST_VERSION_MISMATCH; 'wrapper' additionally resolves the wrapper chain and can return VAST_WRAPPER_DEPTH_EXCEEDED. Absent means 'structural'. See the VAST Validation section of the video channel documentation for the normative checks at each level." ), NoExplicitNull, + OmitUnsetDefault, ] = VastValidation.structural @@ -6562,6 +6607,7 @@ class Portfolio(AdCPBaseModel): description="Whether `publisher_domains` enumerates every publisher this seller represents. Absent or `true` means the list is complete as served. `false` means the seller represents more publishers than it published — because the real set exceeds the advisory item bound or the per-field byte budget — and the list is a sample. This flag is advisory and affects prefetch planning only, never an authorization decision: buyers MUST NOT treat `publisher_domains` as an allowlist whether it is marked complete or not, and MUST resolve each product's publisher through that publisher's own adagents.json. Added in 3.3 so that bounding this counterparty-asserted array cannot silently turn a large but legitimate network into a seller that looks unauthorized for most of its inventory." ), NoExplicitNull, + OmitUnsetDefault, ] = True primary_channels: Annotated[ list[MediaChannel] | None, @@ -6698,8 +6744,8 @@ class Format5(AdCPBaseModel): ), NoExplicitNull, ] = None - canonical_formats_only: Annotated[StrictBool | None, NoExplicitNull] = False - experimental: Annotated[StrictBool | None, NoExplicitNull] = False + canonical_formats_only: Annotated[StrictBool | None, NoExplicitNull, OmitUnsetDefault] = False + experimental: Annotated[StrictBool | None, NoExplicitNull, OmitUnsetDefault] = False format_shape: Annotated[str | None, NoExplicitNull] = None v1_format_ref: Annotated[list[V1FormatRefItem] | None, Field(min_length=1), NoExplicitNull] = ( None @@ -6759,6 +6805,7 @@ class SupportedFormat(AdCPBaseModel): min_length=1, ), NoExplicitNull, + OmitUnsetDefault, ] = [Operation4.build] @@ -6779,6 +6826,7 @@ class Creative(AdCPBaseModel): description='When true, this agent hosts a creative library and supports list_creatives and creative_id references in build_creative. Creative agents with a library should also implement the accounts protocol (sync_accounts / list_accounts) so buyers can establish access.' ), NoExplicitNull, + OmitUnsetDefault, ] = False stable_creative_ids: Annotated[ StrictBool | None, @@ -6786,6 +6834,7 @@ class Creative(AdCPBaseModel): description="When true, this agent gives every creative that entered its library outside sync_creatives (seller UI or API, automation, or another system) a creative_id that is stable for the creative's lifetime and unique within the account, so a buyer connecting to an existing advertiser can reference those creatives in later requests. The agent MUST keep such IDs stable, MUST NOT reuse a creative_id a buyer has used on the account, and under sync_creatives delete_missing MUST NOT archive those creatives. Requires has_creative_library: true. When false or absent, sellers SHOULD follow the same rules but buyers MUST NOT rely on them." ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_revisions: Annotated[ StrictBool | None, @@ -6793,6 +6842,7 @@ class Creative(AdCPBaseModel): description='When true, this agent accepts buyer-assigned revision_id on sync_creatives, enforces immutable revision content, echoes accepted revision identity, returns it from list_creatives, and attributes delivered executions to it. Revision support does not imply revision history, rollback, or staged activation.' ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_assignment_merge: Annotated[ StrictBool | None, @@ -6800,6 +6850,7 @@ class Creative(AdCPBaseModel): description="When true, this agent accepts the merge operation in sync_creatives assignment_operations: a buyer declares that a seller-assigned creative assigned to a package is the same creative as its own, and the seller moves that assignment's serving object, approval, and delivery continuity to the buyer's creative_id. Requires has_creative_library: true and stable_creative_ids: true. When false or absent, the agent rejects merge with UNSUPPORTED_FEATURE and buyers MUST NOT send it." ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_assignment_trackers: Annotated[ StrictBool | None, @@ -6807,6 +6858,7 @@ class Creative(AdCPBaseModel): description="When true, this agent accepts trackers on creative assignments in create_media_buy, update_media_buy, and sync_creatives, fires them in addition to the creative's own trackers or rejects the assignment, enforces shared format tracker-slot limits, and returns the assignment-scoped tracker list in list_creatives and get_media_buys readback. Requires has_creative_library: true. When false or absent, the agent MUST reject the field with UNSUPPORTED_FEATURE and buyers MUST NOT send it." ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_assignment_landing_page: Annotated[ StrictBool | None, @@ -6814,6 +6866,7 @@ class Creative(AdCPBaseModel): description='When true, this agent accepts landing_page_url on creative assignments in create_media_buy, update_media_buy, and sync_creatives, serves that destination on the specified package or rejects the assignment, and returns the effective serving destination when unique in list_creatives and get_media_buys assignment readback. Requires has_creative_library: true. When false or absent, the agent MUST reject the field with UNSUPPORTED_FEATURE and buyers MUST NOT send it.' ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_generation: Annotated[ StrictBool | None, @@ -6821,6 +6874,7 @@ class Creative(AdCPBaseModel): description='When true, this agent can generate creatives from natural language briefs via build_creative. The buyer provides a message with creative direction, and the agent produces a manifest with generated assets. When false, build_creative only supports transformation or library retrieval.' ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_transformation: Annotated[ StrictBool | None, @@ -6828,6 +6882,7 @@ class Creative(AdCPBaseModel): description='When true, this agent can transform or resize existing canonical manifests via build_creative. The buyer supplies a creative_manifest and an advertised target_capability_id.' ), NoExplicitNull, + OmitUnsetDefault, ] = False representation_resolution: Annotated[ RepresentationResolution | None, @@ -6842,6 +6897,7 @@ class Creative(AdCPBaseModel): description='When true, this agent exposes account-scoped creative transformers via list_transformers (the creative analog of media-buy products) and accepts transformer_id + config on build_creative. Buyers SHOULD call list_transformers to discover available transformers, their typed config params (and account-scoped enumerable option values via expand_params), and pricing. When false or absent, the agent does not offer the transformer surface.' ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_refinement: Annotated[ StrictBool | None, @@ -6849,6 +6905,7 @@ class Creative(AdCPBaseModel): description="When true, this agent retains produced build_variant leaves for an agent-defined retention window and can re-build from one via build_creative's refine_from_build_variant_id — applying a natural-language instruction in message plus an optional config delta, returning new lineage-linked variants. A build-time agent capability independent of generation/transformation. When false or absent, refine_from_build_variant_id is rejected with UNSUPPORTED_FEATURE; buyers refine instead via the transform path (creative_manifest + message)." ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_spend_controls: Annotated[ StrictBool | None, @@ -6856,6 +6913,7 @@ class Creative(AdCPBaseModel): description='When true, build_creative honors a per-call `max_spend` ceiling (producing partial paid results and returning budget_status:"capped" + a BUDGET_CAP_REACHED advisory rather than overspending) AND supports mode:"estimate" dry-runs (a projected cost band, producing/billing nothing). When false or absent, max_spend / mode:estimate are rejected with UNSUPPORTED_FEATURE. Out-of-band billers (bills_through_adcp:false) have no AdCP cost truth to cap against, so this is meaningful only alongside bills_through_adcp:true.' ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_evaluator: Annotated[ StrictBool | None, @@ -6863,6 +6921,7 @@ class Creative(AdCPBaseModel): description="Experimental (x-status: experimental) — agents setting this true MUST also list `creative.evaluator` in `experimental_features`; the surface MAY change between 3.x releases with notice (see docs/reference/experimental-status). When true, build_creative accepts an advisory `evaluator` input (exemplars / agent_url, plus an optional `feature_requirement[]` gate, a `rank_by` ordering, and an allowlisted `feature_agent` pointer). Feature discovery uses this response's governance.creative_features catalog: rank_by, feature_requirement, and eval.features[] all share the same creative-feature vocabulary as get_creative_features. A seller's house evaluator is an agent it lists in `creative_policy.accepted_verifiers[]`, named via `agent_url`; its emitted feature_ids still come from this catalog. The evaluator populates a per-leaf `eval` block of creative-feature values (creative-feature-result[], the same shape get_creative_features returns) on BuildCreativeVariantSuccess leaves, which is what the recommended/rank it sets on the best_of_n axis are computed over. The agent runs a gate-then-rank pipeline over its best_of_n exploration: it evaluates each leaf, DROPS leaves failing `feature_requirement[]` from its recommended survivors, then orders survivors by `rank_by`. The gate is internal pruning of which leaves the agent recommends/returns from its own exploration — it never blocks an already-produced billable leaf: what is produced and billed is governed by max_variants/max_creatives/max_spend, not the evaluator. When the evaluator names an external agent, it MUST appear in `creative_policy.accepted_verifiers[]` (off-list → EVALUATOR_AGENT_NOT_ACCEPTED), and the producing agent authenticates the outbound evaluator call on the transport. Evaluator credentials and caller-supplied trust material MUST NOT be passed in the build_creative payload; credential- or trust-material payload keys should be rejected with CREDENTIAL_IN_ARGS. When false or absent, the `evaluator` input is ignored and no `eval` block is emitted." ), NoExplicitNull, + OmitUnsetDefault, ] = False refinable_retention_seconds: Annotated[ SchemaInt | None, @@ -6906,6 +6965,7 @@ class Creative(AdCPBaseModel): description='When true, this creative agent bills through the AdCP rate-card surface: list_creatives returns pricing_options when include_pricing=true with an authenticated account, build_creative populates pricing_option_id and vendor_cost on the response, and report_usage accepts records against the rate card. When false or absent, the agent bills out of band (flat license, SaaS contract, bundled enterprise agreement) and buyers should skip pricing fields and tolerate report_usage returning accepted: 0 with errors carrying BILLING_OUT_OF_BAND. A pre-call discriminator so buyer agents can route across many creative agents without first establishing an account to probe pricing.' ), NoExplicitNull, + OmitUnsetDefault, ] = False canonical_catalog_version: Annotated[ str | None, @@ -6930,6 +6990,7 @@ class WebhookSigning(AdCPBaseModel): description='Identifier of the webhook-signing profile version the agent emits. Value MUST match the `tag=` parameter emitted in the RFC 9421 `Signature-Input` header (see docs/building/by-layer/L3/webhooks.mdx) so receivers can statically validate the declared profile against the on-wire tag. Closed enum; future profile revisions will extend this enum in a follow-up schema bump.' ), NoExplicitNull, + OmitUnsetDefault, ] = None algorithms: Annotated[ list[Algorithm] | None, @@ -6946,6 +7007,7 @@ class WebhookSigning(AdCPBaseModel): description='Whether this agent will fall back to HMAC-SHA256 on the legacy push_notification_config.authentication, accounts[].notification_configs[].authentication, sync_principal.configuration.notification_configs[].authentication, sync_agent_notification_configs.notification_configs[].authentication, reporting_webhook.authentication, or create_media_buy.artifact_webhook.authentication paths for receivers that have not adopted RFC 9421. Deprecated; removed in AdCP 4.0.', ), NoExplicitNull, + OmitUnsetDefault, ] = False delivery_retry_horizon_seconds: Annotated[ SchemaInt | None, @@ -6963,6 +7025,7 @@ class WebhookSigning(AdCPBaseModel): min_length=1, ), NoExplicitNull, + OmitUnsetDefault, ] = [WebhookSigningProfile.adcp_webhook_signing_v1] @@ -7256,6 +7319,7 @@ class MediaBuy(AdCPBaseModel): min_length=1, ), NoExplicitNull, + OmitUnsetDefault, ] = [BuyingMode.brief] measurement_terms_acceptance: Annotated[ StrictBool | None, @@ -7263,6 +7327,7 @@ class MediaBuy(AdCPBaseModel): description="Whether this seller can accept the default measurement_terms it advertises on a product. A value of true means the seller can return a product carrying measurement_terms for a measurement-specific brief and accept those terms unchanged on a package for that product. This opts the seller into conformance scenarios that discover and replay the product's own terms. False or absent means acceptance is outside the seller's advertised scope, but the seller must still reject unsupported terms with TERMS_REJECTED rather than being graded on an acceptance path it did not claim." ), NoExplicitNull, + OmitUnsetDefault, ] = False availability_horizon: Annotated[ StrictBool | None, @@ -7270,6 +7335,7 @@ class MediaBuy(AdCPBaseModel): description='Whether this seller supports flexible-window availability discovery: parsing offer_filters.availability_horizon and answering with time-dimensioned forecast points that carry availability_status. Sellers declaring true MUST apply the full window contract — half-open non-overlapping windows that partition the requested horizon (or signal gaps via incomplete[]), with availability_status computed from all booking eligibility constraints, not only competing holds. false or absent means flexible-window support is unknown: buyers SHOULD use exact start_date/end_date filtering, and sellers MAY ignore the field or reject it. Conformance storyboards gate flexible-window checks on this declaration.' ), NoExplicitNull, + OmitUnsetDefault, ] = False lifecycle_tools: Annotated[ list[LifecycleTool] | None, @@ -7330,6 +7396,7 @@ class MediaBuy(AdCPBaseModel): description='Conformance declaration that this seller supports proposals through either the compact request/refine/finalize lifecycle or the legacy get_products facade. accept_proposal, or the create_media_buy compatibility facade, consumes a finalized committed proposal_id before expires_at.' ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_exclusive_product_ids: Annotated[ StrictBool | None, @@ -7337,6 +7404,7 @@ class MediaBuy(AdCPBaseModel): description='Experimental (media_buy.delegated_composition). Declares that this seller honors criteria.product_ids_exclusive: when a request sets it, the listed product_ids are the complete composable universe, and every proposal purchase, echoed product, and seller-selected signal resolves within them — a brief unsatisfiable within the universe returns rejected (request_proposals) or constraint_unsatisfiable (refine_proposals), never off-universe results. Refinements inherit the universe and may only narrow it. Buyers MUST NOT send the flag unless this is true. Sellers that do not declare this capability MUST NOT silently ignore the flag on request_proposals or refine_proposals; they reject the request at task level with UNSUPPORTED_FEATURE. Primary use is delegated composition, where the requesting platform owns the sellable catalog and the responding agent supplies only the composition judgment.' ), NoExplicitNull, + OmitUnsetDefault, ] = False outcome_target: Annotated[ StrictBool | None, @@ -7344,6 +7412,7 @@ class MediaBuy(AdCPBaseModel): description="Whether this seller supports reverse-forecast planning: parsing criteria.outcome_target (a compact goal — a forecastable-metric delivery metric, an event-type conversion event, or a vendor_metric — plus a desired volume, a cost_per target, or both) and planning against it, answering with total_budget_guidance and forecasts whose points carry the goal's key in metrics, or its (vendor, metric_id) in vendor_metric_values[] for a vendor_metric goal; see outcome-target.json for cost_per answers and rejections. This flag does not distinguish sellers that plan cost_per or vendor_metric goals; buyers rely on the negotiated adcp_version together with features.bidding_policy, conversion_tracking.supported_targets, and vendor_metric_optimization. false or absent means support is unknown: buyers SHOULD express outcome goals in brief prose instead, and sellers reject a structured outcome_target on proposal requests with UNSUPPORTED_FEATURE rather than silently ignoring it (list_products ignores it for every seller)." ), NoExplicitNull, + OmitUnsetDefault, ] = False governance_aware: Annotated[ StrictBool | None, @@ -7351,6 +7420,7 @@ class MediaBuy(AdCPBaseModel): description='Compatibility claim used by existing media-buy conformance runners. A value of true corresponds only to online governance consultation for create_media_buy, the historically graded surface. Agents use adcp.governance_enforcement for explicit task-scoped claims, including update_media_buy and cross-role signed-context enforcement.' ), NoExplicitNull, + OmitUnsetDefault, ] = False propagation_surfaces: Annotated[ list[PropagationSurface] | None, @@ -7359,6 +7429,7 @@ class MediaBuy(AdCPBaseModel): min_length=1, ), NoExplicitNull, + OmitUnsetDefault, ] = [PropagationSurface.snapshot] creative_approval_mode: Annotated[ CreativeApprovalMode | None, @@ -7548,6 +7619,7 @@ class GetAdcpCapabilitiesResponse(AdCPBaseModel): description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." ), NoExplicitNull, + OmitUnsetDefault, ] = False adcp_error: Annotated[ AdcpError | None, diff --git a/src/adcp/types/domains/collection/collection_list.py b/src/adcp/types/domains/collection/collection_list.py index b9689f6b8..7aeac687f 100644 --- a/src/adcp/types/domains/collection/collection_list.py +++ b/src/adcp/types/domains/collection/collection_list.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: collection/collection_list.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl, WireDatetime from pydantic import ConfigDict, Field from ..core import account_ref, brand_ref @@ -60,6 +60,7 @@ class CollectionList(AdCPBaseModel): ge=1, ), NoExplicitNull, + OmitUnsetDefault, ] = 168 created_at: Annotated[ WireDatetime | None, Field(description='When the list was created'), NoExplicitNull diff --git a/src/adcp/types/domains/collection/create_collection_list_response.py b/src/adcp/types/domains/collection/create_collection_list_response.py index fe0deed94..b7301fb86 100644 --- a/src/adcp/types/domains/collection/create_collection_list_response.py +++ b/src/adcp/types/domains/collection/create_collection_list_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: collection/create_collection_list_response.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import context as context_1 @@ -37,6 +37,7 @@ class CreateCollectionListResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEn description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/collection/delete_collection_list_response.py b/src/adcp/types/domains/collection/delete_collection_list_response.py index a700dade7..2e73e5075 100644 --- a/src/adcp/types/domains/collection/delete_collection_list_response.py +++ b/src/adcp/types/domains/collection/delete_collection_list_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: collection/delete_collection_list_response.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import context as context_1 @@ -29,6 +29,7 @@ class DeleteCollectionListResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEn description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/collection/get_collection_list_request.py b/src/adcp/types/domains/collection/get_collection_list_request.py index 668048a89..cb084242f 100644 --- a/src/adcp/types/domains/collection/get_collection_list_request.py +++ b/src/adcp/types/domains/collection/get_collection_list_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: collection/get_collection_list_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import account_ref @@ -25,6 +25,7 @@ class Pagination(AdCPBaseModel): SchemaInt | None, Field(description='Maximum number of collections to return per page', ge=1, le=10000), NoExplicitNull, + OmitUnsetDefault, ] = 1000 cursor: Annotated[ str | None, @@ -51,6 +52,7 @@ class GetCollectionListRequest(AdcpRequest, AdcpVersionEnvelope): description='Whether to apply filters and return resolved collections (default: true)' ), NoExplicitNull, + OmitUnsetDefault, ] = True pagination: Annotated[ Pagination | None, diff --git a/src/adcp/types/domains/collection/update_collection_list_response.py b/src/adcp/types/domains/collection/update_collection_list_response.py index 2495a5fdd..136f2e56c 100644 --- a/src/adcp/types/domains/collection/update_collection_list_response.py +++ b/src/adcp/types/domains/collection/update_collection_list_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: collection/update_collection_list_response.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import context as context_1 @@ -31,6 +31,7 @@ class UpdateCollectionListResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEn description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/compliance/comply_test_controller_request.py b/src/adcp/types/domains/compliance/comply_test_controller_request.py index 89a342bd3..ce2a5f91a 100644 --- a/src/adcp/types/domains/compliance/comply_test_controller_request.py +++ b/src/adcp/types/domains/compliance/comply_test_controller_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: compliance/comply_test_controller_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -10,7 +10,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictFloat @@ -311,6 +311,7 @@ class Params(AdCPBaseModel): description='Where reporting_core_lifecycle_probe restate_after_received leaves the virtual clock relative to the returned stale_received_grace_deadline. within_grace grades the delayed projection; past_grace grades the escalation to action_required. Repeating the operation is convergent: it reuses the committed restatement and only moves the clock.' ), NoExplicitNull, + OmitUnsetDefault, ] = AdvanceTo.within_grace catalog_id: Annotated[ str | None, diff --git a/src/adcp/types/domains/compliance/get_creative_features_completion.py b/src/adcp/types/domains/compliance/get_creative_features_completion.py index 2206975c7..0ecb04b88 100644 --- a/src/adcp/types/domains/compliance/get_creative_features_completion.py +++ b/src/adcp/types/domains/compliance/get_creative_features_completion.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: compliance/get_creative_features_completion.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictFloat from ..core import creative_consumption @@ -17,7 +17,7 @@ class GetCreativeFeaturesComplianceCompletion(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) - status: Literal['completed'] = 'completed' + status: Annotated[Literal['completed'], OmitUnsetDefault] = 'completed' evaluation_id: Annotated[str, Field(min_length=1)] results: list[creative_feature_result.CreativeFeatureResult] pricing_option_id: Annotated[str | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/content_standards/artifact.py b/src/adcp/types/domains/content_standards/artifact.py index 4e5f2d695..9ec9a7bb3 100644 --- a/src/adcp/types/domains/content_standards/artifact.py +++ b/src/adcp/types/domains/content_standards/artifact.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: content_standards/artifact.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl, WireDatetime -from pydantic import ConfigDict, Field, RootModel +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl, WireDatetime +from pydantic import ConfigDict, Field from ..core import format_id as format_id_1 from ..core import provenance as provenance_1 @@ -122,7 +122,7 @@ class AssetAccess3(AdCPBaseModel): method: Literal['signed_url'] = 'signed_url' -class AssetAccess(RootModel[AssetAccess1 | AssetAccess2 | AssetAccess3]): +class AssetAccess(AdCPRootModel[AssetAccess1 | AssetAccess2 | AssetAccess3]): root: Annotated[ AssetAccess1 | AssetAccess2 | AssetAccess3, Field( @@ -160,6 +160,7 @@ class Assets1(AdCPBaseModel): description='MIME type indicating how to parse the content field. Default: text/plain.' ), NoExplicitNull, + OmitUnsetDefault, ] = ContentFormat.text_plain language: Annotated[ str | None, @@ -226,6 +227,7 @@ class Assets3(AdCPBaseModel): description='MIME type indicating how to parse the transcript field. Default: text/plain.' ), NoExplicitNull, + OmitUnsetDefault, ] = TranscriptFormat.text_plain transcript_source: Annotated[ TranscriptSource | None, @@ -265,6 +267,7 @@ class Assets4(AdCPBaseModel): description='MIME type indicating how to parse the transcript field. Default: text/plain.' ), NoExplicitNull, + OmitUnsetDefault, ] = TranscriptFormat.text_plain transcript_source: Annotated[ TranscriptSource1 | None, diff --git a/src/adcp/types/domains/content_standards/get_media_buy_artifacts_request.py b/src/adcp/types/domains/content_standards/get_media_buy_artifacts_request.py index cdd039be1..27d23b1ab 100644 --- a/src/adcp/types/domains/content_standards/get_media_buy_artifacts_request.py +++ b/src/adcp/types/domains/content_standards/get_media_buy_artifacts_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: content_standards/get_media_buy_artifacts_request.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from pydantic import ConfigDict, Field, StrictBool from ..core import account_ref @@ -34,6 +34,7 @@ class Pagination(AdCPBaseModel): SchemaInt | None, Field(description='Maximum number of artifacts to return per page', ge=1, le=10000), NoExplicitNull, + OmitUnsetDefault, ] = 1000 cursor: Annotated[ str | None, @@ -62,6 +63,7 @@ class GetMediaBuyArtifactsRequest(AdcpRequest, AdcpVersionEnvelope): description="When true, only return artifacts where the seller's local model returned local_verdict: 'fail'. Useful for auditing false positives. Not useful when the seller does not run a local evaluation model (all verdicts are 'unevaluated')." ), NoExplicitNull, + OmitUnsetDefault, ] = False time_range: Annotated[ TimeRange | None, Field(description='Filter to specific time period'), NoExplicitNull diff --git a/src/adcp/types/domains/content_standards/validate_content_delivery_request.py b/src/adcp/types/domains/content_standards/validate_content_delivery_request.py index 367bbd864..1c1f19054 100644 --- a/src/adcp/types/domains/content_standards/validate_content_delivery_request.py +++ b/src/adcp/types/domains/content_standards/validate_content_delivery_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: content_standards/validate_content_delivery_request.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from pydantic import Field, StrictBool from ..core import context as context_1 @@ -68,7 +68,10 @@ class ValidateContentDeliveryRequest(AdcpRequest, AdcpVersionEnvelope): NoExplicitNull, ] = None include_passed: Annotated[ - StrictBool | None, Field(description='Include passed records in results'), NoExplicitNull + StrictBool | None, + Field(description='Include passed records in results'), + NoExplicitNull, + OmitUnsetDefault, ] = True context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/core/acceptance_policy_profile_ids.py b/src/adcp/types/domains/core/acceptance_policy_profile_ids.py index dfaf4589f..4ac79b5a5 100644 --- a/src/adcp/types/domains/core/acceptance_policy_profile_ids.py +++ b/src/adcp/types/domains/core/acceptance_policy_profile_ids.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/acceptance_policy_profile_ids.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,8 @@ from typing import Annotated -from pydantic import Field, RootModel +from adcp.types.base import AdCPRootModel +from pydantic import Field class AcceptancePolicyProfileId(ScalarStr): @@ -16,7 +17,7 @@ class AcceptancePolicyProfileId(ScalarStr): _constraints = {'min_length': 1} -class AcceptancePolicyProfileIds(RootModel[list[AcceptancePolicyProfileId]]): +class AcceptancePolicyProfileIds(AdCPRootModel[list[AcceptancePolicyProfileId]]): root: Annotated[ list[AcceptancePolicyProfileId], Field( diff --git a/src/adcp/types/domains/core/account.py b/src/adcp/types/domains/core/account.py index dd5253785..8da0a8774 100644 --- a/src/adcp/types/domains/core/account.py +++ b/src/adcp/types/domains/core/account.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/account.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl, WireDatetime from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..enums import account_scope as account_scope_1 @@ -104,11 +104,13 @@ class ReportingBucket(AdCPBaseModel): description='File format for delivered files. Parquet, Avro, and ORC use internal compression (the top-level compression field is ignored for these formats).' ), NoExplicitNull, + OmitUnsetDefault, ] = Format.jsonl compression: Annotated[ Compression | None, Field(description='Compression applied to delivered files'), NoExplicitNull, + OmitUnsetDefault, ] = Compression.gzip file_retention_days: Annotated[ SchemaInt, diff --git a/src/adcp/types/domains/core/account_authorization.py b/src/adcp/types/domains/core/account_authorization.py index 2b35f018d..466c9d023 100644 --- a/src/adcp/types/domains/core/account_authorization.py +++ b/src/adcp/types/domains/core/account_authorization.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/account_authorization.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool @@ -55,4 +55,5 @@ class AccountAuthorization(AdCPBaseModel): description='Convenience flag. When true, the caller is permitted only non-mutating tasks. Sellers MUST reject any mutation from a read-only caller with READ_ONLY_SCOPE. For the AdCP 3.2 product split, read-only permits list_products but rejects request_proposals, refine_proposals, and decline_proposals; a legacy get_products call is permitted only when the seller can guarantee the selected arm is a synchronous side-effect-free read. Sellers MAY omit this field; omission is equivalent to `false`. Callers MUST NOT infer read-only from `allowed_tasks` alone — the seller MUST set this explicitly when it applies.' ), NoExplicitNull, + OmitUnsetDefault, ] = False diff --git a/src/adcp/types/domains/core/account_change.py b/src/adcp/types/domains/core/account_change.py index 20e612825..457e9969a 100644 --- a/src/adcp/types/domains/core/account_change.py +++ b/src/adcp/types/domains/core/account_change.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/account_change.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireDatetime from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool @@ -119,6 +119,7 @@ class Repair(AdCPBaseModel): description='False when deletion or compelled erasure makes the resource unavailable on the repair read.' ), NoExplicitNull, + OmitUnsetDefault, ] = True unavailable_reason: Annotated[UnavailableReason | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/core/account_identity_change_preview.py b/src/adcp/types/domains/core/account_identity_change_preview.py index d1a32b9a1..a11b8d8ad 100644 --- a/src/adcp/types/domains/core/account_identity_change_preview.py +++ b/src/adcp/types/domains/core/account_identity_change_preview.py @@ -1,15 +1,15 @@ # generated by datamodel-codegen: # filename: core/account_identity_change_preview.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from . import operator_identity @@ -55,7 +55,7 @@ class NonblockingImpact(Impact): effect: Annotated[Effect1 | None, NoExplicitNull] = None -class NonblockingImpacts(RootModel[list[NonblockingImpact]]): +class NonblockingImpacts(AdCPRootModel[list[NonblockingImpact]]): root: Annotated[ list[NonblockingImpact], Field( @@ -66,7 +66,7 @@ class NonblockingImpacts(RootModel[list[NonblockingImpact]]): ] -class BlockedImpacts(RootModel[list[Impact]]): +class BlockedImpacts(AdCPRootModel[list[Impact]]): root: Annotated[ list[Impact], Field( diff --git a/src/adcp/types/domains/core/account_ref.py b/src/adcp/types/domains/core/account_ref.py index 53911305d..a6060e482 100644 --- a/src/adcp/types/domains/core/account_ref.py +++ b/src/adcp/types/domains/core/account_ref.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/account_ref.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from . import brand_ref @@ -68,6 +68,7 @@ class AccountReference2(AdCPBaseModel): description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' ), NoExplicitNull, + OmitUnsetDefault, ] = False diff --git a/src/adcp/types/domains/core/agent_notification_config.py b/src/adcp/types/domains/core/agent_notification_config.py index 2ed681b0a..69750da8c 100644 --- a/src/adcp/types/domains/core/agent_notification_config.py +++ b/src/adcp/types/domains/core/agent_notification_config.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/agent_notification_config.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl from pydantic import ConfigDict, Field, StrictBool from ..enums import auth_scheme, notification_type, webhook_signing_profile @@ -67,6 +67,7 @@ class AgentNotificationConfig(AdCPBaseModel): description='When true, the seller also fires caller-eligible notification types added to the enum by later AdCP versions — but only types classified invalidation-only, whose payloads carry identifiers and a repair pointer rather than domain data. Payload-bearing types always require explicit enumeration in event_types; this flag never silently opts a caller into more-sensitive payloads. There is deliberately no wildcard event type. Receivers setting this MUST tolerate unknown notification_type values.' ), NoExplicitNull, + OmitUnsetDefault, ] = False authentication: Annotated[ Authentication | None, @@ -89,5 +90,6 @@ class AgentNotificationConfig(AdCPBaseModel): description='When false, the seller persists the configuration but suppresses fires. Use to pause a subscriber without losing the registration. Paused configs may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time. Reactivation requires full SSRF validation with connect pinning plus proof-of-control for any tuple without current valid proof.' ), NoExplicitNull, + OmitUnsetDefault, ] = True ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/core/agent_notification_config_state.py b/src/adcp/types/domains/core/agent_notification_config_state.py index 2dbf51b20..708c36279 100644 --- a/src/adcp/types/domains/core/agent_notification_config_state.py +++ b/src/adcp/types/domains/core/agent_notification_config_state.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/agent_notification_config_state.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl from pydantic import ConfigDict, Field, StrictBool from ..enums import auth_scheme, notification_type @@ -40,5 +40,5 @@ class AgentNotificationConfigState(AdCPBaseModel): NoExplicitNull, ] = None authentication: Annotated[Authentication | None, Field(deprecated=True), NoExplicitNull] = None - active: Annotated[StrictBool | None, NoExplicitNull] = True + active: Annotated[StrictBool | None, NoExplicitNull, OmitUnsetDefault] = True ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/core/asset_group_vocabulary.py b/src/adcp/types/domains/core/asset_group_vocabulary.py index 782e1078e..96b227b81 100644 --- a/src/adcp/types/domains/core/asset_group_vocabulary.py +++ b/src/adcp/types/domains/core/asset_group_vocabulary.py @@ -1,15 +1,16 @@ # generated by datamodel-codegen: # filename: core/asset_group_vocabulary.json -# timestamp: 2026-06-01T00:32:59+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Any -from pydantic import Field, RootModel +from adcp.types.base import AdCPRootModel +from pydantic import Field -class AdcpAssetGroupVocabularyRegistry(RootModel[Any]): +class AdcpAssetGroupVocabularyRegistry(AdCPRootModel[Any]): root: Annotated[ Any, Field( diff --git a/src/adcp/types/domains/core/assets/asset_union.py b/src/adcp/types/domains/core/assets/asset_union.py index 418e70515..7c04809a8 100644 --- a/src/adcp/types/domains/core/assets/asset_union.py +++ b/src/adcp/types/domains/core/assets/asset_union.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/assets/asset_union.json -# timestamp: 2026-10-28T15:22:03+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl, WireDatetime +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl, WireDatetime from adcp.types._scalar import ScalarFloat, ScalarStr from pydantic import ( ConfigDict, @@ -276,7 +276,7 @@ class UnavailableBehavior(StrEnum): reject = 'reject' -class MacroBearingUrl1(RootModel[WireAnyUrl]): +class MacroBearingUrl1(AdCPRootModel[WireAnyUrl]): root: Annotated[ WireAnyUrl, Field( @@ -417,7 +417,7 @@ class UniversalMacro2(ScalarStr): } -class UniversalMacro(RootModel[UniversalMacro1 | UniversalMacro2]): +class UniversalMacro(AdCPRootModel[UniversalMacro1 | UniversalMacro2]): root: Annotated[ UniversalMacro1 | UniversalMacro2, Field( @@ -546,7 +546,11 @@ class Location6(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) - field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' + field: Annotated[ + Literal['url'], + Field(description='Asset field containing this occurrence.'), + OmitUnsetDefault, + ] = 'url' occurrence: Annotated[ SchemaInt, Field( @@ -617,7 +621,9 @@ class Location8(AdCPBaseModel): extra='forbid', ) field: Annotated[ - Literal['content'], Field(description='Asset field containing this occurrence.') + Literal['content'], + Field(description='Asset field containing this occurrence.'), + OmitUnsetDefault, ] = 'content' occurrence: Annotated[ SchemaInt, @@ -999,6 +1005,7 @@ class CatalogFieldMapping(AdCPBaseModel): description="For transform 'split': the separator character or string to split on. Defaults to ','." ), NoExplicitNull, + OmitUnsetDefault, ] = ',' default: Annotated[ Any | None, @@ -1780,13 +1787,14 @@ class WebhookAsset(AdCPBaseModel): ), ] = 'webhook' url: Annotated[WireAnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: Annotated[HttpMethod | None, Field(description='HTTP method'), NoExplicitNull] = ( - HttpMethod.POST - ) + method: Annotated[ + HttpMethod | None, Field(description='HTTP method'), NoExplicitNull, OmitUnsetDefault + ] = HttpMethod.POST timeout_ms: Annotated[ SchemaInt | None, Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), NoExplicitNull, + OmitUnsetDefault, ] = 500 supported_macros: Annotated[ list[UniversalMacro | str] | None, @@ -2125,7 +2133,7 @@ class DaastAsset2(AdCPBaseModel): content: Annotated[str, Field(description='Inline DAAST XML content')] -class DaastAsset(RootModel[DaastAsset1 | DaastAsset2]): +class DaastAsset(AdCPRootModel[DaastAsset1 | DaastAsset2]): root: Annotated[ DaastAsset1 | DaastAsset2, Field( @@ -2171,6 +2179,7 @@ class MarkdownAsset(AdCPBaseModel): description='Markdown flavor used. CommonMark for strict compatibility, GFM for tables/task lists/strikethrough.' ), NoExplicitNull, + OmitUnsetDefault, ] = MarkdownFlavor.commonmark allow_raw_html: Annotated[ StrictBool | None, @@ -2178,6 +2187,7 @@ class MarkdownAsset(AdCPBaseModel): description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -2475,7 +2485,7 @@ class PublishedPostAsset2(AdCPBaseModel): ] = None -class PublishedPostAsset(RootModel[PublishedPostAsset1 | PublishedPostAsset2]): +class PublishedPostAsset(AdCPRootModel[PublishedPostAsset1 | PublishedPostAsset2]): root: Annotated[ PublishedPostAsset1 | PublishedPostAsset2, Field( @@ -2861,7 +2871,7 @@ class VastAsset2(AdCPBaseModel): content: Annotated[str, Field(description='Inline VAST XML content')] -class VastAsset(RootModel[VastAsset1 | VastAsset2]): +class VastAsset(AdCPRootModel[VastAsset1 | VastAsset2]): root: Annotated[ VastAsset1 | VastAsset2, Field( @@ -3407,7 +3417,7 @@ class DisplayTagAsset3(AdCPBaseModel): ] -class DisplayTagAsset(RootModel[DisplayTagAsset1 | DisplayTagAsset2 | DisplayTagAsset3]): +class DisplayTagAsset(AdCPRootModel[DisplayTagAsset1 | DisplayTagAsset2 | DisplayTagAsset3]): root: Annotated[ DisplayTagAsset1 | DisplayTagAsset2 | DisplayTagAsset3, Field( @@ -3583,6 +3593,7 @@ class PixelTrackerAsset(AdCPBaseModel): description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." ), NoExplicitNull, + OmitUnsetDefault, ] = Method.img url: Annotated[ MacroBearingUrl, @@ -3661,6 +3672,7 @@ class VastTrackerAsset(AdCPBaseModel): description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. Defaults to `linear`. Existing 3.x assets remain structurally permissive; a tracker execution contract applies the standards-valid event/target matrix when matching a creative to a product.' ), NoExplicitNull, + OmitUnsetDefault, ] = Target.linear provenance: Annotated[ Provenance | None, @@ -3718,6 +3730,7 @@ class DaastTrackerAsset(AdCPBaseModel): description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7). DAAST has no `` element. Defaults to `linear`. Existing 3.x assets remain structurally permissive; a tracker execution contract applies the standards-valid event/target matrix when matching a creative to a product.' ), NoExplicitNull, + OmitUnsetDefault, ] = Target1.linear provenance: Annotated[ Provenance | None, diff --git a/src/adcp/types/domains/core/assets/daast_asset.py b/src/adcp/types/domains/core/assets/daast_asset.py index 9c50c486e..27fdd3caf 100644 --- a/src/adcp/types/domains/core/assets/daast_asset.py +++ b/src/adcp/types/domains/core/assets/daast_asset.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/assets/daast_asset.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Any, Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl -from pydantic import ConfigDict, Field, RootModel, StrictBool +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl +from pydantic import ConfigDict, Field, StrictBool from ...enums import daast_tracking_event from ...enums import daast_version as daast_version_1 @@ -29,7 +29,11 @@ class Location(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) - field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' + field: Annotated[ + Literal['url'], + Field(description='Asset field containing this occurrence.'), + OmitUnsetDefault, + ] = 'url' occurrence: Annotated[ SchemaInt, Field( @@ -51,7 +55,9 @@ class Location13(AdCPBaseModel): extra='forbid', ) field: Annotated[ - Literal['content'], Field(description='Asset field containing this occurrence.') + Literal['content'], + Field(description='Asset field containing this occurrence.'), + OmitUnsetDefault, ] = 'content' occurrence: Annotated[ SchemaInt, @@ -353,7 +359,7 @@ class DaastAsset4(AdCPBaseModel): content: Annotated[str, Field(description='Inline DAAST XML content')] -class DaastAsset(RootModel[DaastAsset3 | DaastAsset4]): +class DaastAsset(AdCPRootModel[DaastAsset3 | DaastAsset4]): root: Annotated[ DaastAsset3 | DaastAsset4, Field( diff --git a/src/adcp/types/domains/core/assets/daast_tracker_asset.py b/src/adcp/types/domains/core/assets/daast_tracker_asset.py index 81bb26308..2f5b0aa0a 100644 --- a/src/adcp/types/domains/core/assets/daast_tracker_asset.py +++ b/src/adcp/types/domains/core/assets/daast_tracker_asset.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/assets/daast_tracker_asset.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field from ...enums import daast_tracking_event @@ -29,7 +29,11 @@ class Location(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) - field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' + field: Annotated[ + Literal['url'], + Field(description='Asset field containing this occurrence.'), + OmitUnsetDefault, + ] = 'url' occurrence: Annotated[ SchemaInt, Field( @@ -91,6 +95,7 @@ class DaastTrackerAsset(AdCPBaseModel): description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7). DAAST has no `` element. Defaults to `linear`. Existing 3.x assets remain structurally permissive; a tracker execution contract applies the standards-valid event/target matrix when matching a creative to a product.' ), NoExplicitNull, + OmitUnsetDefault, ] = Target.linear provenance: Annotated[ provenance_1.Provenance | None, diff --git a/src/adcp/types/domains/core/assets/display_tag_asset.py b/src/adcp/types/domains/core/assets/display_tag_asset.py index b37522f5e..8276d6d1b 100644 --- a/src/adcp/types/domains/core/assets/display_tag_asset.py +++ b/src/adcp/types/domains/core/assets/display_tag_asset.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/assets/display_tag_asset.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Any, Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl -from pydantic import ConfigDict, Field, RootModel, StrictBool +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull, SchemaInt, WireAnyUrl +from pydantic import ConfigDict, Field, StrictBool from .. import macro_bearing_url from .. import provenance as provenance_1 @@ -510,7 +510,7 @@ class DisplayTagAsset6(AdCPBaseModel): ] -class DisplayTagAsset(RootModel[DisplayTagAsset4 | DisplayTagAsset5 | DisplayTagAsset6]): +class DisplayTagAsset(AdCPRootModel[DisplayTagAsset4 | DisplayTagAsset5 | DisplayTagAsset6]): root: Annotated[ DisplayTagAsset4 | DisplayTagAsset5 | DisplayTagAsset6, Field( diff --git a/src/adcp/types/domains/core/assets/markdown_asset.py b/src/adcp/types/domains/core/assets/markdown_asset.py index a9cbe5c53..71e2c30d5 100644 --- a/src/adcp/types/domains/core/assets/markdown_asset.py +++ b/src/adcp/types/domains/core/assets/markdown_asset.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/assets/markdown_asset.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ...enums import markdown_flavor as markdown_flavor_1 @@ -41,6 +41,7 @@ class MarkdownAsset(AdCPBaseModel): description='Markdown flavor used. CommonMark for strict compatibility, GFM for tables/task lists/strikethrough.' ), NoExplicitNull, + OmitUnsetDefault, ] = markdown_flavor_1.MarkdownFlavor.commonmark allow_raw_html: Annotated[ StrictBool | None, @@ -48,4 +49,5 @@ class MarkdownAsset(AdCPBaseModel): description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' ), NoExplicitNull, + OmitUnsetDefault, ] = False diff --git a/src/adcp/types/domains/core/assets/pixel_tracker_asset.py b/src/adcp/types/domains/core/assets/pixel_tracker_asset.py index 2bbd47b0e..486575418 100644 --- a/src/adcp/types/domains/core/assets/pixel_tracker_asset.py +++ b/src/adcp/types/domains/core/assets/pixel_tracker_asset.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/assets/pixel_tracker_asset.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field from ...enums import pixel_tracking_event @@ -34,7 +34,11 @@ class Location(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) - field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' + field: Annotated[ + Literal['url'], + Field(description='Asset field containing this occurrence.'), + OmitUnsetDefault, + ] = 'url' occurrence: Annotated[ SchemaInt, Field( @@ -70,6 +74,7 @@ class PixelTrackerAsset(AdCPBaseModel): description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." ), NoExplicitNull, + OmitUnsetDefault, ] = Method.img url: Annotated[ macro_bearing_url.MacroBearingUrl, diff --git a/src/adcp/types/domains/core/assets/url_asset.py b/src/adcp/types/domains/core/assets/url_asset.py index 6d8bd47af..6fea8b994 100644 --- a/src/adcp/types/domains/core/assets/url_asset.py +++ b/src/adcp/types/domains/core/assets/url_asset.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/assets/url_asset.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field from ...enums import url_asset_type @@ -29,7 +29,11 @@ class Location(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) - field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' + field: Annotated[ + Literal['url'], + Field(description='Asset field containing this occurrence.'), + OmitUnsetDefault, + ] = 'url' occurrence: Annotated[ SchemaInt, Field( diff --git a/src/adcp/types/domains/core/assets/vast_asset.py b/src/adcp/types/domains/core/assets/vast_asset.py index 194b293a3..a022cc00a 100644 --- a/src/adcp/types/domains/core/assets/vast_asset.py +++ b/src/adcp/types/domains/core/assets/vast_asset.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/assets/vast_asset.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Any, Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl -from pydantic import ConfigDict, Field, RootModel, StrictBool +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl +from pydantic import ConfigDict, Field, StrictBool from ...enums import vast_tracking_event from ...enums import vast_version as vast_version_1 @@ -29,7 +29,11 @@ class Location(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) - field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' + field: Annotated[ + Literal['url'], + Field(description='Asset field containing this occurrence.'), + OmitUnsetDefault, + ] = 'url' occurrence: Annotated[ SchemaInt, Field( @@ -51,7 +55,9 @@ class Location26(AdCPBaseModel): extra='forbid', ) field: Annotated[ - Literal['content'], Field(description='Asset field containing this occurrence.') + Literal['content'], + Field(description='Asset field containing this occurrence.'), + OmitUnsetDefault, ] = 'content' occurrence: Annotated[ SchemaInt, @@ -363,7 +369,7 @@ class VastAsset4(AdCPBaseModel): content: Annotated[str, Field(description='Inline VAST XML content')] -class VastAsset(RootModel[VastAsset3 | VastAsset4]): +class VastAsset(AdCPRootModel[VastAsset3 | VastAsset4]): root: Annotated[ VastAsset3 | VastAsset4, Field( diff --git a/src/adcp/types/domains/core/assets/vast_tracker_asset.py b/src/adcp/types/domains/core/assets/vast_tracker_asset.py index b8b1169cb..97d6bd092 100644 --- a/src/adcp/types/domains/core/assets/vast_tracker_asset.py +++ b/src/adcp/types/domains/core/assets/vast_tracker_asset.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/assets/vast_tracker_asset.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field from ...enums import vast_tracking_event @@ -29,7 +29,11 @@ class Location(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) - field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' + field: Annotated[ + Literal['url'], + Field(description='Asset field containing this occurrence.'), + OmitUnsetDefault, + ] = 'url' occurrence: Annotated[ SchemaInt, Field( @@ -92,6 +96,7 @@ class VastTrackerAsset(AdCPBaseModel): description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. Defaults to `linear`. Existing 3.x assets remain structurally permissive; a tracker execution contract applies the standards-valid event/target matrix when matching a creative to a product.' ), NoExplicitNull, + OmitUnsetDefault, ] = Target.linear provenance: Annotated[ provenance_1.Provenance | None, diff --git a/src/adcp/types/domains/core/assets/webhook_asset.py b/src/adcp/types/domains/core/assets/webhook_asset.py index 5e6424488..fc4f72f26 100644 --- a/src/adcp/types/domains/core/assets/webhook_asset.py +++ b/src/adcp/types/domains/core/assets/webhook_asset.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/assets/webhook_asset.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl from pydantic import ConfigDict, Field from ...enums import http_method, universal_macro, webhook_response_type, webhook_security_method @@ -39,12 +39,16 @@ class WebhookAsset(AdCPBaseModel): ] = 'webhook' url: Annotated[WireAnyUrl, Field(description='Webhook URL to call for dynamic content')] method: Annotated[ - http_method.HttpMethod | None, Field(description='HTTP method'), NoExplicitNull + http_method.HttpMethod | None, + Field(description='HTTP method'), + NoExplicitNull, + OmitUnsetDefault, ] = http_method.HttpMethod.POST timeout_ms: Annotated[ SchemaInt | None, Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), NoExplicitNull, + OmitUnsetDefault, ] = 500 supported_macros: Annotated[ list[universal_macro.UniversalMacro | str] | None, diff --git a/src/adcp/types/domains/core/attestation_capabilities.py b/src/adcp/types/domains/core/attestation_capabilities.py index 8f61fae29..065345903 100644 --- a/src/adcp/types/domains/core/attestation_capabilities.py +++ b/src/adcp/types/domains/core/attestation_capabilities.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/attestation_capabilities.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl -from pydantic import ConfigDict, Field, RootModel +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull, SchemaInt, WireAnyUrl +from pydantic import ConfigDict, Field from . import attestation_issuer from . import ext as ext_1 @@ -20,7 +20,7 @@ class SupportedDeliveryMethod(StrEnum): embedded = 'embedded' -class CredentialOrigin(RootModel[WireAnyUrl]): +class CredentialOrigin(AdCPRootModel[WireAnyUrl]): root: WireAnyUrl diff --git a/src/adcp/types/domains/core/audience_characteristic.py b/src/adcp/types/domains/core/audience_characteristic.py index bd6bb2eb6..39b7d42a2 100644 --- a/src/adcp/types/domains/core/audience_characteristic.py +++ b/src/adcp/types/domains/core/audience_characteristic.py @@ -1,19 +1,19 @@ # generated by datamodel-codegen: # filename: core/audience_characteristic.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Any, Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl -from pydantic import ConfigDict, Field, RootModel, StrictBool, StrictFloat, model_validator +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull, WireAnyUrl +from pydantic import ConfigDict, Field, StrictBool, StrictFloat, model_validator from pydantic_core import PydanticCustomError -class Dimension(RootModel[WireAnyUrl]): +class Dimension(AdCPRootModel[WireAnyUrl]): root: Annotated[ WireAnyUrl, Field( @@ -22,7 +22,7 @@ class Dimension(RootModel[WireAnyUrl]): ] -class Value(RootModel[list[str | StrictFloat | StrictBool]]): +class Value(AdCPRootModel[list[str | StrictFloat | StrictBool]]): root: Annotated[ list[str | StrictFloat | StrictBool], Field(description='Scalar or set value for the dimension.', min_length=1), diff --git a/src/adcp/types/domains/core/audience_evidence.py b/src/adcp/types/domains/core/audience_evidence.py index f29545a1f..781fb45df 100644 --- a/src/adcp/types/domains/core/audience_evidence.py +++ b/src/adcp/types/domains/core/audience_evidence.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/audience_evidence.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Any, Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl, WireDatetime -from pydantic import ConfigDict, Field, RootModel, StrictFloat +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull, SchemaInt, WireAnyUrl, WireDatetime +from pydantic import ConfigDict, Field, StrictFloat from ..enums import audience_evidence_methodology, audience_resolution_method, audience_subject_type from . import audience_characteristic, brand_ref, date_range @@ -235,7 +235,7 @@ class Subject17(Subject14): ) -class Subject(RootModel[Subject15 | Subject16 | Subject17]): +class Subject(AdCPRootModel[Subject15 | Subject16 | Subject17]): root: Annotated[ Subject15 | Subject16 | Subject17, Field( @@ -277,7 +277,7 @@ class Subject25(Subject21): ] -class Subject2(RootModel[Subject25 | Subject26 | Subject27]): +class Subject2(AdCPRootModel[Subject25 | Subject26 | Subject27]): root: Annotated[ Subject25 | Subject26 | Subject27, Field( @@ -344,7 +344,7 @@ class Subject35(Subject31): ] = None -class Subject3(RootModel[Subject35 | Subject36 | Subject37]): +class Subject3(AdCPRootModel[Subject35 | Subject36 | Subject37]): root: Annotated[ Subject35 | Subject36 | Subject37, Field( diff --git a/src/adcp/types/domains/core/audience_evidence_requirements.py b/src/adcp/types/domains/core/audience_evidence_requirements.py index f409c29f0..efe7df3c9 100644 --- a/src/adcp/types/domains/core/audience_evidence_requirements.py +++ b/src/adcp/types/domains/core/audience_evidence_requirements.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/audience_evidence_requirements.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..enums import audience_evidence_methodology, audience_resolution_method, audience_subject_type @@ -120,6 +120,7 @@ class AudienceEvidenceRequirements(AdCPBaseModel): StrictBool | None, Field(description='Whether an admissible item must carry methodology_url.'), NoExplicitNull, + OmitUnsetDefault, ] = False independent_attestation_required: Annotated[ StrictBool | None, @@ -127,6 +128,7 @@ class AudienceEvidenceRequirements(AdCPBaseModel): description="Whether an admissible item must have at least one exact reference/evaluation pair whose outcome is verified, whose reference is published in the evidence, and whose claim type and issuer match the buyer's accepted lists as well as the seller's issuer and resolver policy. Setting true requires accepted_attestation_issuers." ), NoExplicitNull, + OmitUnsetDefault, ] = False accepted_attestation_issuers: Annotated[ list[attestation_issuer.AttestationIssuer] | None, diff --git a/src/adcp/types/domains/core/bidding_policy.py b/src/adcp/types/domains/core/bidding_policy.py index 5b131de30..e1ae9de4a 100644 --- a/src/adcp/types/domains/core/bidding_policy.py +++ b/src/adcp/types/domains/core/bidding_policy.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/bidding_policy.json -# timestamp: 2026-10-21T12:09:21+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictFloat @@ -68,6 +68,7 @@ class BiddingPolicy(AdCPBaseModel): Field( description='Explicitly use seller/provider automatic bidding at this authored scope. At package scope this is a complete override of a media-buy policy, not inheritance. It MUST be the only field in the block and MUST be preserved on readback.' ), + OmitUnsetDefault, NoExplicitNull, ] = None bid_amount: Annotated[ diff --git a/src/adcp/types/domains/core/budget_allocation.py b/src/adcp/types/domains/core/budget_allocation.py index 1cca97cbe..5908b4098 100644 --- a/src/adcp/types/domains/core/budget_allocation.py +++ b/src/adcp/types/domains/core/budget_allocation.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/budget_allocation.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictFloat from ..enums import event_type as event_type_1 @@ -193,6 +193,7 @@ class EventSource(AdCPBaseModel): description="Unit-scaling multiplier the seller must apply to value_field before aggregation. Use -1 for refund events (negate the value), 0.01 for values in cents, -0.01 for refunds in cents. It MUST NOT be used for currency conversion. A value of 0 zeroes out this source's value contribution (the source still counts for event dedup). Defaults to 1. This is not passed as a parameter to underlying platform APIs — the seller applies it when computing aggregated value metrics." ), NoExplicitNull, + OmitUnsetDefault, ] = 1 diff --git a/src/adcp/types/domains/core/canonical_account_ref.py b/src/adcp/types/domains/core/canonical_account_ref.py index 425c6b81f..396126a53 100644 --- a/src/adcp/types/domains/core/canonical_account_ref.py +++ b/src/adcp/types/domains/core/canonical_account_ref.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/canonical_account_ref.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from . import brand_key @@ -51,7 +51,7 @@ class CanonicalAccountReference2(AdCPBaseModel): ), NoExplicitNull, ] = None - sandbox: Annotated[StrictBool | None, NoExplicitNull] = False + sandbox: Annotated[StrictBool | None, NoExplicitNull, OmitUnsetDefault] = False CanonicalAccountReference = Annotated[ diff --git a/src/adcp/types/domains/core/canonical_format_option.py b/src/adcp/types/domains/core/canonical_format_option.py index 24fab62ef..d2b748023 100644 --- a/src/adcp/types/domains/core/canonical_format_option.py +++ b/src/adcp/types/domains/core/canonical_format_option.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/canonical_format_option.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl from pydantic import ConfigDict, Field, StrictBool from ..enums import channels @@ -81,8 +81,8 @@ class CanonicalFormatOption(AdCPBaseModel): locale_policy: Annotated[creative_locale_policy.CreativeLocalePolicy | None, NoExplicitNull] = ( None ) - canonical_formats_only: Annotated[StrictBool | None, NoExplicitNull] = False - experimental: Annotated[StrictBool | None, NoExplicitNull] = False + canonical_formats_only: Annotated[StrictBool | None, NoExplicitNull, OmitUnsetDefault] = False + experimental: Annotated[StrictBool | None, NoExplicitNull, OmitUnsetDefault] = False format_kind: FormatKind params: dict[str, Any] format_shape: Annotated[str | None, Field(min_length=1), NoExplicitNull] = None diff --git a/src/adcp/types/domains/core/canonical_optimization_goal.py b/src/adcp/types/domains/core/canonical_optimization_goal.py index be23c45d7..3c02cdd26 100644 --- a/src/adcp/types/domains/core/canonical_optimization_goal.py +++ b/src/adcp/types/domains/core/canonical_optimization_goal.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/canonical_optimization_goal.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictFloat from ..enums import event_type as event_type_1 @@ -81,7 +81,7 @@ class EventSource(AdCPBaseModel): event_type: event_type_1.EventType custom_event_name: Annotated[str | None, Field(min_length=1), NoExplicitNull] = None value_field: Annotated[str | None, Field(min_length=1), NoExplicitNull] = None - value_factor: Annotated[StrictFloat | None, NoExplicitNull] = 1 + value_factor: Annotated[StrictFloat | None, NoExplicitNull, OmitUnsetDefault] = 1 class CanonicalOptimizationGoal2(AdCPBaseModel): diff --git a/src/adcp/types/domains/core/canonical_product.py b/src/adcp/types/domains/core/canonical_product.py index 62e26d7f8..a8f5614b8 100644 --- a/src/adcp/types/domains/core/canonical_product.py +++ b/src/adcp/types/domains/core/canonical_product.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/canonical_product.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireDatetime from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool @@ -219,6 +219,7 @@ class CanonicalProduct(AdCPBaseModel): description="Whether buyers can select a subset of this product's collections through targeting_overlay.collection_list, collection_lists, or collection_selection. When false, the product is a fixed bundle (a collection_selection that exactly restates the complete bundle remains an inherent match)." ), NoExplicitNull, + OmitUnsetDefault, ] = False delivery_type: Annotated[delivery_type_1.DeliveryType | None, NoExplicitNull] = None exclusivity: Annotated[exclusivity_1.Exclusivity | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/core/canonical_projection_slot_override.py b/src/adcp/types/domains/core/canonical_projection_slot_override.py index c8f797fc9..fb05ee790 100644 --- a/src/adcp/types/domains/core/canonical_projection_slot_override.py +++ b/src/adcp/types/domains/core/canonical_projection_slot_override.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/canonical_projection_slot_override.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictBool @@ -30,6 +30,7 @@ class CanonicalProjectionSlotOverride(AdCPBaseModel): StrictBool | None, Field(description='Whether the slot is required in the projected declaration.'), NoExplicitNull, + OmitUnsetDefault, ] = False max_chars: Annotated[ SchemaInt | None, @@ -42,4 +43,5 @@ class CanonicalProjectionSlotOverride(AdCPBaseModel): description="When false, slot is for moderation/review only and is NOT consumed by the seller's renderer (e.g., a brand-safety brief that informs review but doesn't appear in the rendered ad)." ), NoExplicitNull, + OmitUnsetDefault, ] = True diff --git a/src/adcp/types/domains/core/canvas_constraint.py b/src/adcp/types/domains/core/canvas_constraint.py index 9183a1f1d..85f2907c6 100644 --- a/src/adcp/types/domains/core/canvas_constraint.py +++ b/src/adcp/types/domains/core/canvas_constraint.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/canvas_constraint.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictFloat @@ -31,7 +31,7 @@ class Region(AdCPBaseModel): y: Annotated[StrictFloat, Field(ge=0.0)] width: Annotated[StrictFloat, Field(gt=0.0)] height: Annotated[StrictFloat, Field(gt=0.0)] - unit: Annotated[Unit | None, NoExplicitNull] = Unit.px + unit: Annotated[Unit | None, NoExplicitNull, OmitUnsetDefault] = Unit.px class CanvasConstraint(AdCPBaseModel): diff --git a/src/adcp/types/domains/core/catalog_field_mapping.py b/src/adcp/types/domains/core/catalog_field_mapping.py index cac824da7..754969540 100644 --- a/src/adcp/types/domains/core/catalog_field_mapping.py +++ b/src/adcp/types/domains/core/catalog_field_mapping.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/catalog_field_mapping.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictFloat from . import ext as ext_1 @@ -86,6 +86,7 @@ class CatalogFieldMapping(AdCPBaseModel): description="For transform 'split': the separator character or string to split on. Defaults to ','." ), NoExplicitNull, + OmitUnsetDefault, ] = ',' default: Annotated[ Any | None, diff --git a/src/adcp/types/domains/core/creative_asset.py b/src/adcp/types/domains/core/creative_asset.py index 64856645d..94df9898c 100644 --- a/src/adcp/types/domains/core/creative_asset.py +++ b/src/adcp/types/domains/core/creative_asset.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/creative_asset.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull -from pydantic import ConfigDict, Field, RootModel, StrictFloat, StringConstraints, model_validator +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull +from pydantic import ConfigDict, Field, StrictFloat, StringConstraints, model_validator from ..enums import creative_status from . import creative_assets @@ -40,7 +40,7 @@ class Input(AdCPBaseModel): ] = None -class Assets(RootModel[list[asset_union.AssetVariant]]): +class Assets(AdCPRootModel[list[asset_union.AssetVariant]]): root: Annotated[list[asset_union.AssetVariant], Field(min_length=1)] diff --git a/src/adcp/types/domains/core/creative_assets.py b/src/adcp/types/domains/core/creative_assets.py index ebfb08581..fda3b317a 100644 --- a/src/adcp/types/domains/core/creative_assets.py +++ b/src/adcp/types/domains/core/creative_assets.py @@ -1,22 +1,23 @@ # generated by datamodel-codegen: # filename: core/creative_assets.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Any, Annotated -from pydantic import Field, RootModel, StringConstraints +from adcp.types.base import AdCPRootModel +from pydantic import Field, StringConstraints from .assets import asset_union -class CreativeAssets1(RootModel[list[asset_union.AssetVariant]]): +class CreativeAssets1(AdCPRootModel[list[asset_union.AssetVariant]]): root: Annotated[list[asset_union.AssetVariant], Field(min_length=1)] class CreativeAssets( - RootModel[dict[Annotated[str, StringConstraints(pattern=r'^')], asset_union.AssetVariant | CreativeAssets1]] + AdCPRootModel[dict[Annotated[str, StringConstraints(pattern=r'^')], asset_union.AssetVariant | CreativeAssets1]] ): root: Annotated[ dict[Annotated[str, StringConstraints(pattern=r'^')], asset_union.AssetVariant | CreativeAssets1], diff --git a/src/adcp/types/domains/core/creative_localization.py b/src/adcp/types/domains/core/creative_localization.py index 2e009526e..c4afa05bd 100644 --- a/src/adcp/types/domains/core/creative_localization.py +++ b/src/adcp/types/domains/core/creative_localization.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/creative_localization.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull -from pydantic import ConfigDict, Field, RootModel, StringConstraints +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull +from pydantic import ConfigDict, Field, StringConstraints from . import locale_tag, localized_creative_asset @@ -53,7 +53,7 @@ class LocaleFallback(AdCPBaseModel): ] -class Assets(RootModel[list[localized_creative_asset.LocalizedCreativeAsset]]): +class Assets(AdCPRootModel[list[localized_creative_asset.LocalizedCreativeAsset]]): root: Annotated[list[localized_creative_asset.LocalizedCreativeAsset], Field(min_length=1)] diff --git a/src/adcp/types/domains/core/creative_localization_readback.py b/src/adcp/types/domains/core/creative_localization_readback.py index dd765809d..b483f4ae9 100644 --- a/src/adcp/types/domains/core/creative_localization_readback.py +++ b/src/adcp/types/domains/core/creative_localization_readback.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/creative_localization_readback.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Any, Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull -from pydantic import ConfigDict, Field, RootModel, StringConstraints +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull +from pydantic import ConfigDict, Field, StringConstraints from . import locale_tag, localized_creative_asset @@ -26,12 +26,12 @@ class LocaleFallback(AdCPBaseModel): locale_variant_id: Annotated[str, Field(max_length=255, min_length=1)] -class ResolvedAssets1(RootModel[list[localized_creative_asset.LocalizedCreativeAsset]]): +class ResolvedAssets1(AdCPRootModel[list[localized_creative_asset.LocalizedCreativeAsset]]): root: Annotated[list[localized_creative_asset.LocalizedCreativeAsset], Field(min_length=1)] class ResolvedAssets( - RootModel[ + AdCPRootModel[ dict[ Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], localized_creative_asset.LocalizedCreativeAsset | ResolvedAssets1, diff --git a/src/adcp/types/domains/core/creative_manifest.py b/src/adcp/types/domains/core/creative_manifest.py index 14b23c679..22da8e73f 100644 --- a/src/adcp/types/domains/core/creative_manifest.py +++ b/src/adcp/types/domains/core/creative_manifest.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/creative_manifest.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull -from pydantic import ConfigDict, Field, RootModel, StringConstraints, model_validator +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull +from pydantic import ConfigDict, Field, StringConstraints, model_validator from . import brand_ref, creative_assets from . import ext as ext_1 @@ -31,7 +31,7 @@ def _normalize_asset_models(value: Any) -> Any: return value -class Assets(RootModel[list[asset_union.AssetVariant]]): +class Assets(AdCPRootModel[list[asset_union.AssetVariant]]): root: Annotated[list[asset_union.AssetVariant], Field(min_length=1)] diff --git a/src/adcp/types/domains/core/creative_operation_format_declaration.py b/src/adcp/types/domains/core/creative_operation_format_declaration.py index 3ffa43c7e..6c58c9dfd 100644 --- a/src/adcp/types/domains/core/creative_operation_format_declaration.py +++ b/src/adcp/types/domains/core/creative_operation_format_declaration.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/creative_operation_format_declaration.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl from pydantic import Field, StrictBool from ..enums import channels @@ -52,8 +52,8 @@ class CreativeOperationFormatDeclaration(AdCPBaseModel): locale_policy: Annotated[creative_locale_policy.CreativeLocalePolicy | None, NoExplicitNull] = ( None ) - canonical_formats_only: Annotated[StrictBool | None, NoExplicitNull] = False - experimental: Annotated[StrictBool | None, NoExplicitNull] = False + canonical_formats_only: Annotated[StrictBool | None, NoExplicitNull, OmitUnsetDefault] = False + experimental: Annotated[StrictBool | None, NoExplicitNull, OmitUnsetDefault] = False format_shape: Annotated[str | None, NoExplicitNull] = None v1_format_ref: Annotated[ list[format_id.FormatReferenceStructuredObject] | None, Field(min_length=1), NoExplicitNull diff --git a/src/adcp/types/domains/core/creative_variable.py b/src/adcp/types/domains/core/creative_variable.py index f13e1e825..1f1d5ab39 100644 --- a/src/adcp/types/domains/core/creative_variable.py +++ b/src/adcp/types/domains/core/creative_variable.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/creative_variable.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool @@ -46,4 +46,5 @@ class CreativeVariable(AdCPBaseModel): StrictBool | None, Field(description='Whether this variable must have a value for the creative to serve'), NoExplicitNull, + OmitUnsetDefault, ] = False diff --git a/src/adcp/types/domains/core/daast_tracker_constraints.py b/src/adcp/types/domains/core/daast_tracker_constraints.py index 3d0a9f0f4..b78b3d2a0 100644 --- a/src/adcp/types/domains/core/daast_tracker_constraints.py +++ b/src/adcp/types/domains/core/daast_tracker_constraints.py @@ -1,15 +1,15 @@ # generated by datamodel-codegen: # filename: core/daast_tracker_constraints.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull from adcp.types._scalar import ScalarStr -from pydantic import Field, RootModel +from pydantic import Field from ..enums import daast_version @@ -41,7 +41,7 @@ class DaastOffset(ScalarStr): _constraints = {'pattern': '^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$'} -class DaastVersions(RootModel[list[daast_version.DaastVersion]]): +class DaastVersions(AdCPRootModel[list[daast_version.DaastVersion]]): root: Annotated[list[daast_version.DaastVersion], Field(min_length=1)] diff --git a/src/adcp/types/domains/core/daypart_target.py b/src/adcp/types/domains/core/daypart_target.py index a68dd1a12..c44172790 100644 --- a/src/adcp/types/domains/core/daypart_target.py +++ b/src/adcp/types/domains/core/daypart_target.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/daypart_target.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, model_validator from ..enums import day_of_week @@ -68,6 +68,7 @@ class DaypartTarget(AdCPBaseModel): validate_default=True, ), NoExplicitNull, + OmitUnsetDefault, ] = 'inventory_local' label: Annotated[ str | None, diff --git a/src/adcp/types/domains/core/deadline_policy.py b/src/adcp/types/domains/core/deadline_policy.py index a153d7bb2..838d47e30 100644 --- a/src/adcp/types/domains/core/deadline_policy.py +++ b/src/adcp/types/domains/core/deadline_policy.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/deadline_policy.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictBool @@ -57,4 +57,5 @@ class DeadlinePolicy(AdCPBaseModel): description='When true, lead_days counts business days (Mon-Fri) rather than calendar days. Defaults to false.' ), NoExplicitNull, + OmitUnsetDefault, ] = False diff --git a/src/adcp/types/domains/core/delivery_breakdown_controls.py b/src/adcp/types/domains/core/delivery_breakdown_controls.py index 0ca46bb2c..8a84f4715 100644 --- a/src/adcp/types/domains/core/delivery_breakdown_controls.py +++ b/src/adcp/types/domains/core/delivery_breakdown_controls.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/delivery_breakdown_controls.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field from ..enums import sort_direction as sort_direction_1 @@ -21,6 +21,7 @@ class DeliveryBreakdownControls(AdCPBaseModel): SchemaInt | None, Field(description='Maximum number of rows to return. Defaults to 25.', ge=1), NoExplicitNull, + OmitUnsetDefault, ] = 25 sort_by: Annotated[ sort_metric.SortMetric | None, @@ -28,6 +29,7 @@ class DeliveryBreakdownControls(AdCPBaseModel): description='Metric used to order rows. Falls back to spend when unavailable at this row grain; on fallback sort_direction resets to desc. Rows lacking a value for the applied sort metric order last regardless of direction.' ), NoExplicitNull, + OmitUnsetDefault, ] = sort_metric.SortMetric.spend sort_direction: Annotated[ sort_direction_1.SortDirection | None, @@ -35,4 +37,5 @@ class DeliveryBreakdownControls(AdCPBaseModel): description='Direction for sort_by ordering. Sellers MUST apply the requested direction to the applied sort metric; direction has no availability fallback.' ), NoExplicitNull, + OmitUnsetDefault, ] = sort_direction_1.SortDirection.desc diff --git a/src/adcp/types/domains/core/delivery_metrics.py b/src/adcp/types/domains/core/delivery_metrics.py index 9f93ec264..e2b7f5b58 100644 --- a/src/adcp/types/domains/core/delivery_metrics.py +++ b/src/adcp/types/domains/core/delivery_metrics.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/delivery_metrics.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..enums import action_source as action_source_1 @@ -316,6 +316,7 @@ class Posting(AdCPBaseModel): description='What the record attests: initial posting, rotary rotation to a new face, repair/reposting after damage, or removal at flight end' ), NoExplicitNull, + OmitUnsetDefault, ] = EventType.posted occurred_at: Annotated[ date, diff --git a/src/adcp/types/domains/core/evaluator_spec.py b/src/adcp/types/domains/core/evaluator_spec.py index ad891fa74..3792014f1 100644 --- a/src/adcp/types/domains/core/evaluator_spec.py +++ b/src/adcp/types/domains/core/evaluator_spec.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/evaluator_spec.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl from pydantic import ConfigDict, Field, StrictFloat from ..content_standards import artifact @@ -36,6 +36,7 @@ class RankByItem(AdCPBaseModel): description='Sort direction for this feature: `maximize` ranks higher feature values first (e.g. creative_quality_score), `minimize` ranks lower values first (e.g. predicted_cpa).' ), NoExplicitNull, + OmitUnsetDefault, ] = Direction.maximize diff --git a/src/adcp/types/domains/core/feature_requirement.py b/src/adcp/types/domains/core/feature_requirement.py index bdfafb329..3f1a814ef 100644 --- a/src/adcp/types/domains/core/feature_requirement.py +++ b/src/adcp/types/domains/core/feature_requirement.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/feature_requirement.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictFloat @@ -47,6 +47,7 @@ class FeatureRequirement(AdCPBaseModel): description="How to handle properties where this feature is not covered. 'exclude' (default): property is removed from the list. 'include': property passes this requirement (fail-open)." ), NoExplicitNull, + OmitUnsetDefault, ] = IfNotCovered.exclude policy_id: Annotated[ str | None, diff --git a/src/adcp/types/domains/core/forecast_point_dimensions.py b/src/adcp/types/domains/core/forecast_point_dimensions.py index e760b7340..569f2e099 100644 --- a/src/adcp/types/domains/core/forecast_point_dimensions.py +++ b/src/adcp/types/domains/core/forecast_point_dimensions.py @@ -1,12 +1,13 @@ # generated by datamodel-codegen: # filename: core/forecast_point_dimensions.json -# timestamp: 2026-08-21T04:35:18+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Any, Annotated -from pydantic import Field, RootModel +from adcp.types.base import AdCPRootModel +from pydantic import Field from . import ( forecast_dimension_audience, @@ -20,7 +21,7 @@ class ForecastPointDimensions( - RootModel[ + AdCPRootModel[ list[ forecast_dimension_geo.GeoForecastDimension | forecast_dimension_placement.PlacementForecastDimension diff --git a/src/adcp/types/domains/core/format.py b/src/adcp/types/domains/core/format.py index 1a3a27dfa..b164a4bfd 100644 --- a/src/adcp/types/domains/core/format.py +++ b/src/adcp/types/domains/core/format.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/format.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl -from pydantic import ConfigDict, Field, RootModel, StrictBool, StrictFloat +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl +from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..enums import ( available_metric, @@ -740,6 +740,7 @@ class Assets30(AdCPBaseModel): description="How the platform uses repetitions of this group. 'sequential' means all items display in order (carousels, playlists). 'optimize' means the platform selects the best-performing combination from alternatives (asset group optimization like Meta Advantage+ or Google Pmax)." ), NoExplicitNull, + OmitUnsetDefault, ] = SelectionMode.sequential assets: Annotated[ list[GroupFormatAssetUnion], Field(description='Assets within each repetition of this group') @@ -825,6 +826,7 @@ class CanonicalParameters1(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -832,6 +834,7 @@ class CanonicalParameters1(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -938,6 +941,7 @@ class CanonicalParameters2(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -945,6 +949,7 @@ class CanonicalParameters2(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1051,6 +1056,7 @@ class CanonicalParameters3(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1058,6 +1064,7 @@ class CanonicalParameters3(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1164,6 +1171,7 @@ class CanonicalParameters4(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1171,6 +1179,7 @@ class CanonicalParameters4(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1277,6 +1286,7 @@ class CanonicalParameters5(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1284,6 +1294,7 @@ class CanonicalParameters5(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1390,6 +1401,7 @@ class CanonicalParameters6(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1397,6 +1409,7 @@ class CanonicalParameters6(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1503,6 +1516,7 @@ class CanonicalParameters7(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1510,6 +1524,7 @@ class CanonicalParameters7(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1616,6 +1631,7 @@ class CanonicalParameters8(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1623,6 +1639,7 @@ class CanonicalParameters8(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1729,6 +1746,7 @@ class CanonicalParameters9(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1736,6 +1754,7 @@ class CanonicalParameters9(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1842,6 +1861,7 @@ class CanonicalParameters10(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1849,6 +1869,7 @@ class CanonicalParameters10(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1955,6 +1976,7 @@ class CanonicalParameters11(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1962,6 +1984,7 @@ class CanonicalParameters11(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -2068,6 +2091,7 @@ class CanonicalParameters12(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -2075,6 +2099,7 @@ class CanonicalParameters12(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -2181,6 +2206,7 @@ class CanonicalParameters13(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -2188,6 +2214,7 @@ class CanonicalParameters13(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -2294,6 +2321,7 @@ class CanonicalParameters14(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -2301,6 +2329,7 @@ class CanonicalParameters14(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -2407,6 +2436,7 @@ class CanonicalParameters15(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -2414,6 +2444,7 @@ class CanonicalParameters15(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -2520,6 +2551,7 @@ class CanonicalParameters16(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -2527,6 +2559,7 @@ class CanonicalParameters16(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -2624,7 +2657,7 @@ class CanonicalParameters33(CanonicalParameters16, CanonicalParameters17): class CanonicalParameters( - RootModel[ + AdCPRootModel[ CanonicalParameters18 | CanonicalParameters19 | CanonicalParameters20 diff --git a/src/adcp/types/domains/core/format_shape_vocabulary.py b/src/adcp/types/domains/core/format_shape_vocabulary.py index 780be1cf0..02c98757e 100644 --- a/src/adcp/types/domains/core/format_shape_vocabulary.py +++ b/src/adcp/types/domains/core/format_shape_vocabulary.py @@ -1,15 +1,16 @@ # generated by datamodel-codegen: # filename: core/format_shape_vocabulary.json -# timestamp: 2026-05-22T13:15:12+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Any -from pydantic import Field, RootModel +from adcp.types.base import AdCPRootModel +from pydantic import Field -class AdcpFormatShapeVocabularyRegistry(RootModel[Any]): +class AdcpFormatShapeVocabularyRegistry(AdCPRootModel[Any]): root: Annotated[ Any, Field( diff --git a/src/adcp/types/domains/core/geo_place_system.py b/src/adcp/types/domains/core/geo_place_system.py index cdba4ac00..1411d69c0 100644 --- a/src/adcp/types/domains/core/geo_place_system.py +++ b/src/adcp/types/domains/core/geo_place_system.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/geo_place_system.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import WireAnyUrl -from pydantic import Field, RootModel +from adcp.types.base import AdCPRootModel, WireAnyUrl +from pydantic import Field class GeographicPlaceIdentifierSystem1(StrEnum): @@ -17,7 +17,7 @@ class GeographicPlaceIdentifierSystem1(StrEnum): microsoft_ads = 'microsoft_ads' -class GeographicPlaceIdentifierSystem2(RootModel[WireAnyUrl]): +class GeographicPlaceIdentifierSystem2(AdCPRootModel[WireAnyUrl]): root: Annotated[ WireAnyUrl, Field( diff --git a/src/adcp/types/domains/core/geo_place_type.py b/src/adcp/types/domains/core/geo_place_type.py index 550fc18a6..69f5c2d9d 100644 --- a/src/adcp/types/domains/core/geo_place_type.py +++ b/src/adcp/types/domains/core/geo_place_type.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/geo_place_type.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import WireAnyUrl -from pydantic import Field, RootModel +from adcp.types.base import AdCPRootModel, WireAnyUrl +from pydantic import Field class GeographicPlaceType1(StrEnum): @@ -30,7 +30,7 @@ class GeographicPlaceType1(StrEnum): ward = 'ward' -class GeographicPlaceType2(RootModel[WireAnyUrl]): +class GeographicPlaceType2(AdCPRootModel[WireAnyUrl]): root: Annotated[ WireAnyUrl, Field( diff --git a/src/adcp/types/domains/core/get_geo_place_resolution_request.py b/src/adcp/types/domains/core/get_geo_place_resolution_request.py index e579c7bb1..a025f461f 100644 --- a/src/adcp/types/domains/core/get_geo_place_resolution_request.py +++ b/src/adcp/types/domains/core/get_geo_place_resolution_request.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/get_geo_place_resolution_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, model_validator from . import geo_place_type, locale_tag @@ -59,7 +59,7 @@ class GetGeographicPlaceResolutionRequest(AdCPBaseModel): NoExplicitNull, ] = None cursor: Annotated[str | None, Field(min_length=1), NoExplicitNull] = None - limit: Annotated[SchemaInt | None, Field(ge=1, le=100), NoExplicitNull] = 20 + limit: Annotated[SchemaInt | None, Field(ge=1, le=100), NoExplicitNull, OmitUnsetDefault] = 20 @model_validator(mode='after') def _require_schema_required_group(self) -> GetGeographicPlaceResolutionRequest: diff --git a/src/adcp/types/domains/core/hold_policy.py b/src/adcp/types/domains/core/hold_policy.py index 1a1a8bdb8..e339973de 100644 --- a/src/adcp/types/domains/core/hold_policy.py +++ b/src/adcp/types/domains/core/hold_policy.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/hold_policy.json -# timestamp: 2026-10-27T00:56:00+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool @@ -52,6 +52,7 @@ class HoldPolicy(AdCPBaseModel): description="Whether the seller accepts refine_proposals action extend_hold (experimental, media_buy.hold_extension, gated by get_adcp_capabilities media_buy.proposal_refinement.hold_extension) for a hold placed under this policy. Defaults to false: when false or absent, the seller declines extend_hold with hold_unavailable and buyers SHOULD NOT send it. A draft is extendable only if every allocated product's effective policy is extendable. Each extension successor carries this policy forward unchanged." ), NoExplicitNull, + OmitUnsetDefault, ] = False max_total_duration: Annotated[ PositiveDuration | None, @@ -66,4 +67,5 @@ class HoldPolicy(AdCPBaseModel): description="Experimental (media_buy.hold_ranking). Whether a successful finalize may place this hold behind other buyers' holds on the same inventory. false or absent (the default) keeps the exclusive, first-position meaning of available true: the seller places a first-position hold or returns hold_unavailable. true means the hold is ranked: finalize may succeed with the hold queued behind holds that could prevent it from booking, and the committed proposal reports the buyer's own position in hold_rank (1 = first position). A hold at rank 1 has the same acceptance guarantee as an exclusive hold until expires_at. A hold behind first position (hold_rank 2 or more) does not: while a hold ahead of it is live or has been accepted, the seller MAY decline accept_proposal with PRODUCT_UNAVAILABLE. Ranking is advisory: no right-of-first-refusal deadline and no rank-change notification are defined. A draft is rankable if any allocated product's effective policy is rankable. Allowed only when available is true. Each extend_hold successor carries this policy forward unchanged. For a caller below 3.3, which never receives hold_policy, the seller treats rankable as false." ), NoExplicitNull, + OmitUnsetDefault, ] = False diff --git a/src/adcp/types/domains/core/localized_creative_asset.py b/src/adcp/types/domains/core/localized_creative_asset.py index d76f7af0c..c6444eb6d 100644 --- a/src/adcp/types/domains/core/localized_creative_asset.py +++ b/src/adcp/types/domains/core/localized_creative_asset.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/localized_creative_asset.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Any, Annotated -from adcp.types.base import AdCPBaseModel -from pydantic import Field, RootModel +from adcp.types.base import AdCPBaseModel, AdCPRootModel +from pydantic import Field from .assets.asset_union import ( AudioAsset, @@ -120,7 +120,7 @@ class LocalizedCreativeAsset23(DaastTrackerAsset, LocalizedCreativeAsset1): class LocalizedCreativeAsset( - RootModel[ + AdCPRootModel[ LocalizedCreativeAsset2 | LocalizedCreativeAsset3 | LocalizedCreativeAsset4 diff --git a/src/adcp/types/domains/core/macro_bearing_url.py b/src/adcp/types/domains/core/macro_bearing_url.py index 3d37abbd3..3e4b64b2c 100644 --- a/src/adcp/types/domains/core/macro_bearing_url.py +++ b/src/adcp/types/domains/core/macro_bearing_url.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/macro_bearing_url.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,11 +8,11 @@ from typing import Annotated -from adcp.types.base import WireAnyUrl -from pydantic import Field, RootModel +from adcp.types.base import AdCPRootModel, WireAnyUrl +from pydantic import Field -class MacroBearingUrl3(RootModel[WireAnyUrl]): +class MacroBearingUrl3(AdCPRootModel[WireAnyUrl]): root: Annotated[ WireAnyUrl, Field( diff --git a/src/adcp/types/domains/core/media_buy.py b/src/adcp/types/domains/core/media_buy.py index 0a598bbee..3e7167345 100644 --- a/src/adcp/types/domains/core/media_buy.py +++ b/src/adcp/types/domains/core/media_buy.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/media_buy.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireDatetime from pydantic import ConfigDict, Field, StrictFloat from ..enums import canceled_by as canceled_by_1 @@ -85,6 +85,7 @@ class MediaBuy(AdCPBaseModel): description="Aggregate health based on open impairments[]. Orthogonal to status — a paused, pending, or active buy can each be impaired. Defaults to 'ok' when impairments[] is empty." ), NoExplicitNull, + OmitUnsetDefault, ] = media_buy_health.MediaBuyHealth.ok impairments: Annotated[ list[impairment.Impairment] | None, diff --git a/src/adcp/types/domains/core/media_buy_support.py b/src/adcp/types/domains/core/media_buy_support.py index a02b6ef39..ce1c46348 100644 --- a/src/adcp/types/domains/core/media_buy_support.py +++ b/src/adcp/types/domains/core/media_buy_support.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/media_buy_support.json -# timestamp: 2026-10-21T12:09:21+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field from . import ext as ext_1 @@ -22,6 +22,7 @@ class ProductMediaBuySupport(AdCPBaseModel): Field( description="This product can participate in the seller's shared counter for a root MediaBuy.frequency_cap. A buy mixing this product with one that lacks this declaration, or whose resolved supported_per_units omits the root cap's per value, is rejected atomically." ), + OmitUnsetDefault, NoExplicitNull, ] = None frequency_cap_constraints: Annotated[ diff --git a/src/adcp/types/domains/core/media_buy_support_requirements.py b/src/adcp/types/domains/core/media_buy_support_requirements.py index 978545067..19473f3fc 100644 --- a/src/adcp/types/domains/core/media_buy_support_requirements.py +++ b/src/adcp/types/domains/core/media_buy_support_requirements.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/media_buy_support_requirements.json -# timestamp: 2026-10-21T12:09:21+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field from . import media_buy_frequency_cap_requirement @@ -21,6 +21,7 @@ class ProductMediaBuySupportRequirements(AdCPBaseModel): Field( description='Require product participation in one shared MediaBuy frequency-cap counter.' ), + OmitUnsetDefault, NoExplicitNull, ] = None frequency_cap_constraints: Annotated[ diff --git a/src/adcp/types/domains/core/notification_config.py b/src/adcp/types/domains/core/notification_config.py index c701e3c1d..28c3752ff 100644 --- a/src/adcp/types/domains/core/notification_config.py +++ b/src/adcp/types/domains/core/notification_config.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/notification_config.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl from pydantic import ConfigDict, Field, StrictBool from ..enums import auth_scheme, webhook_signing_profile @@ -91,6 +91,7 @@ class NotificationConfig(AdCPBaseModel): description='Product webhook representation selected by this subscriber. Use canonical with lifecycle_tools.list_products; legacy is the default for 3.x get_products consumers. Sellers emit exactly canonical_product/canonical_pricing_options or product/pricing_options accordingly. Valid only when event_types includes a product.* event.' ), NoExplicitNull, + OmitUnsetDefault, ] = ProductPayloadView.legacy authentication: Annotated[ Authentication | None, @@ -113,5 +114,6 @@ class NotificationConfig(AdCPBaseModel): description="When false, the seller persists the configuration but suppresses fires. Use to pause a noisy subscriber without losing the registration. Sellers MUST NOT skip persisting the entry when `active: false` — the buyer's next `sync_accounts` MUST observe the same array, otherwise the buyer cannot distinguish pause from drop. Paused configs may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time. Reactivation requires full SSRF validation with connect pinning plus proof-of-control for any tuple without current valid proof." ), NoExplicitNull, + OmitUnsetDefault, ] = True ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/core/opportunity_context.py b/src/adcp/types/domains/core/opportunity_context.py index f76cf749d..da571e4ab 100644 --- a/src/adcp/types/domains/core/opportunity_context.py +++ b/src/adcp/types/domains/core/opportunity_context.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/opportunity_context.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireDatetime from pydantic import ConfigDict, Field, StrictBool from . import date_range, duration @@ -52,7 +52,10 @@ class LaterProposals(AdCPBaseModel): WireDatetime | None, Field(description='Later-proposal window end.'), NoExplicitNull ] = None max_count: Annotated[ - SchemaInt | None, Field(description='Defaults to 5.', ge=1, le=50), NoExplicitNull + SchemaInt | None, + Field(description='Defaults to 5.', ge=1, le=50), + NoExplicitNull, + OmitUnsetDefault, ] = 5 min_interval: Annotated[ duration.Duration | None, Field(description='Defaults to 1 day.'), NoExplicitNull diff --git a/src/adcp/types/domains/core/optimization_goal.py b/src/adcp/types/domains/core/optimization_goal.py index b2515d606..6cc4601c7 100644 --- a/src/adcp/types/domains/core/optimization_goal.py +++ b/src/adcp/types/domains/core/optimization_goal.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/optimization_goal.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictFloat from ..enums import event_type as event_type_1 @@ -121,6 +121,7 @@ class EventSource(AdCPBaseModel): description="Unit-scaling multiplier the seller must apply to value_field before aggregation. Use -1 for refund events (negate the value), 0.01 for values in cents, -0.01 for refunds in cents. It MUST NOT be used for currency conversion. A value of 0 zeroes out this source's value contribution (the source still counts for event dedup). Defaults to 1. This is not passed as a parameter to underlying platform APIs — the seller applies it when computing aggregated value metrics." ), NoExplicitNull, + OmitUnsetDefault, ] = 1 diff --git a/src/adcp/types/domains/core/package.py b/src/adcp/types/domains/core/package.py index 48f1616ff..c0443ee8e 100644 --- a/src/adcp/types/domains/core/package.py +++ b/src/adcp/types/domains/core/package.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/package.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..enums import canceled_by as canceled_by_1 @@ -303,6 +303,7 @@ class Package(AdCPBaseModel): description='Whether this package is paused by the buyer. Paused packages do not deliver impressions. Defaults to false.' ), NoExplicitNull, + OmitUnsetDefault, ] = False canceled: Annotated[ StrictBool | None, @@ -310,6 +311,7 @@ class Package(AdCPBaseModel): description='Whether this package has been canceled. Canceled packages stop delivery and cannot be reactivated. Defaults to false.' ), NoExplicitNull, + OmitUnsetDefault, ] = False cancellation: Annotated[ Cancellation | None, diff --git a/src/adcp/types/domains/core/package_format_snapshot.py b/src/adcp/types/domains/core/package_format_snapshot.py index c2a9b107d..503786446 100644 --- a/src/adcp/types/domains/core/package_format_snapshot.py +++ b/src/adcp/types/domains/core/package_format_snapshot.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/package_format_snapshot.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl from pydantic import ConfigDict, Field, StrictBool from ..enums import channels, daast_version, vast_version @@ -177,6 +177,7 @@ class PackageFormatSnapshot1(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -184,6 +185,7 @@ class PackageFormatSnapshot1(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -290,6 +292,7 @@ class PackageFormatSnapshot2(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -297,6 +300,7 @@ class PackageFormatSnapshot2(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -403,6 +407,7 @@ class PackageFormatSnapshot3(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -410,6 +415,7 @@ class PackageFormatSnapshot3(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -516,6 +522,7 @@ class PackageFormatSnapshot4(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -523,6 +530,7 @@ class PackageFormatSnapshot4(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -629,6 +637,7 @@ class PackageFormatSnapshot5(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -636,6 +645,7 @@ class PackageFormatSnapshot5(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -742,6 +752,7 @@ class PackageFormatSnapshot6(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -749,6 +760,7 @@ class PackageFormatSnapshot6(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -855,6 +867,7 @@ class PackageFormatSnapshot7(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -862,6 +875,7 @@ class PackageFormatSnapshot7(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -968,6 +982,7 @@ class PackageFormatSnapshot8(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -975,6 +990,7 @@ class PackageFormatSnapshot8(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1081,6 +1097,7 @@ class PackageFormatSnapshot9(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1088,6 +1105,7 @@ class PackageFormatSnapshot9(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1194,6 +1212,7 @@ class PackageFormatSnapshot10(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1201,6 +1220,7 @@ class PackageFormatSnapshot10(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1307,6 +1327,7 @@ class PackageFormatSnapshot11(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1314,6 +1335,7 @@ class PackageFormatSnapshot11(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1420,6 +1442,7 @@ class PackageFormatSnapshot12(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1427,6 +1450,7 @@ class PackageFormatSnapshot12(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1533,6 +1557,7 @@ class PackageFormatSnapshot13(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1540,6 +1565,7 @@ class PackageFormatSnapshot13(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1646,6 +1672,7 @@ class PackageFormatSnapshot14(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1653,6 +1680,7 @@ class PackageFormatSnapshot14(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1759,6 +1787,7 @@ class PackageFormatSnapshot15(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1766,6 +1795,7 @@ class PackageFormatSnapshot15(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1872,6 +1902,7 @@ class PackageFormatSnapshot16(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1879,6 +1910,7 @@ class PackageFormatSnapshot16(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, diff --git a/src/adcp/types/domains/core/pagination_request.py b/src/adcp/types/domains/core/pagination_request.py index 633a44acb..1c28e7103 100644 --- a/src/adcp/types/domains/core/pagination_request.py +++ b/src/adcp/types/domains/core/pagination_request.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/pagination_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field @@ -18,6 +18,7 @@ class PaginationRequest(AdCPBaseModel): SchemaInt | None, Field(description='Maximum number of items to return per page', ge=1, le=100), NoExplicitNull, + OmitUnsetDefault, ] = 50 cursor: Annotated[ str | None, diff --git a/src/adcp/types/domains/core/placement_identity.py b/src/adcp/types/domains/core/placement_identity.py index 22bbeca3d..92876cdb5 100644 --- a/src/adcp/types/domains/core/placement_identity.py +++ b/src/adcp/types/domains/core/placement_identity.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/placement_identity.json -# timestamp: 2026-10-04T04:53:46+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Any, Annotated, Literal -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from adcp.types.base import AdCPBaseModel, AdCPRootModel +from pydantic import ConfigDict, Field from . import seller_agent_ref @@ -45,7 +45,7 @@ class PlacementIdentity2(AdCPBaseModel): ] -class PlacementIdentity(RootModel[PlacementIdentity1 | PlacementIdentity2]): +class PlacementIdentity(AdCPRootModel[PlacementIdentity1 | PlacementIdentity2]): root: Annotated[ PlacementIdentity1 | PlacementIdentity2, Field( diff --git a/src/adcp/types/domains/core/placement_presentation.py b/src/adcp/types/domains/core/placement_presentation.py index 87291298d..fa5e86104 100644 --- a/src/adcp/types/domains/core/placement_presentation.py +++ b/src/adcp/types/domains/core/placement_presentation.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/placement_presentation.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, field_validator @@ -18,9 +18,9 @@ class Canvas(AdCPBaseModel): ) width: Annotated[SchemaInt, Field(ge=1, le=8192)] height: Annotated[SchemaInt, Field(ge=1, le=8192)] - background_color: Annotated[str | None, Field(pattern='^#[0-9A-Fa-f]{6}$'), NoExplicitNull] = ( - '#ffffff' - ) + background_color: Annotated[ + str | None, Field(pattern='^#[0-9A-Fa-f]{6}$'), NoExplicitNull, OmitUnsetDefault + ] = '#ffffff' class Fit(StrEnum): diff --git a/src/adcp/types/domains/core/positive_postal_area_support.py b/src/adcp/types/domains/core/positive_postal_area_support.py index 000bb58c3..14c99772a 100644 --- a/src/adcp/types/domains/core/positive_postal_area_support.py +++ b/src/adcp/types/domains/core/positive_postal_area_support.py @@ -1,26 +1,26 @@ # generated by datamodel-codegen: # filename: core/positive_postal_area_support.json -# timestamp: 2026-10-21T11:25:44+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import Field from .postal_area_support import PostalAreaSupport class PositivePostalAreaSupport(PostalAreaSupport): - us_zip: Annotated[Literal[True] | None, Field(deprecated=True), NoExplicitNull] = None - us_zip_plus_four: Annotated[Literal[True] | None, Field(deprecated=True), NoExplicitNull] = None - gb_outward: Annotated[Literal[True] | None, Field(deprecated=True), NoExplicitNull] = None - gb_full: Annotated[Literal[True] | None, Field(deprecated=True), NoExplicitNull] = None - ca_fsa: Annotated[Literal[True] | None, Field(deprecated=True), NoExplicitNull] = None - ca_full: Annotated[Literal[True] | None, Field(deprecated=True), NoExplicitNull] = None - de_plz: Annotated[Literal[True] | None, Field(deprecated=True), NoExplicitNull] = None - fr_code_postal: Annotated[Literal[True] | None, Field(deprecated=True), NoExplicitNull] = None - au_postcode: Annotated[Literal[True] | None, Field(deprecated=True), NoExplicitNull] = None - ch_plz: Annotated[Literal[True] | None, Field(deprecated=True), NoExplicitNull] = None - at_plz: Annotated[Literal[True] | None, Field(deprecated=True), NoExplicitNull] = None + us_zip: Annotated[Literal[True] | None, Field(deprecated=True), OmitUnsetDefault, NoExplicitNull] = None + us_zip_plus_four: Annotated[Literal[True] | None, Field(deprecated=True), OmitUnsetDefault, NoExplicitNull] = None + gb_outward: Annotated[Literal[True] | None, Field(deprecated=True), OmitUnsetDefault, NoExplicitNull] = None + gb_full: Annotated[Literal[True] | None, Field(deprecated=True), OmitUnsetDefault, NoExplicitNull] = None + ca_fsa: Annotated[Literal[True] | None, Field(deprecated=True), OmitUnsetDefault, NoExplicitNull] = None + ca_full: Annotated[Literal[True] | None, Field(deprecated=True), OmitUnsetDefault, NoExplicitNull] = None + de_plz: Annotated[Literal[True] | None, Field(deprecated=True), OmitUnsetDefault, NoExplicitNull] = None + fr_code_postal: Annotated[Literal[True] | None, Field(deprecated=True), OmitUnsetDefault, NoExplicitNull] = None + au_postcode: Annotated[Literal[True] | None, Field(deprecated=True), OmitUnsetDefault, NoExplicitNull] = None + ch_plz: Annotated[Literal[True] | None, Field(deprecated=True), OmitUnsetDefault, NoExplicitNull] = None + at_plz: Annotated[Literal[True] | None, Field(deprecated=True), OmitUnsetDefault, NoExplicitNull] = None diff --git a/src/adcp/types/domains/core/postal_area.py b/src/adcp/types/domains/core/postal_area.py index b7d6aa29e..be68fe766 100644 --- a/src/adcp/types/domains/core/postal_area.py +++ b/src/adcp/types/domains/core/postal_area.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/postal_area.json -# timestamp: 2026-10-03T00:35:15+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Any, Annotated, Literal -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel, model_validator +from adcp.types.base import AdCPBaseModel, AdCPRootModel, OmitUnsetDefault +from pydantic import ConfigDict, Field, model_validator from ..enums import legacy_postal_system, postal_system @@ -43,7 +43,9 @@ class System(StrEnum): class PostalArea11(AdCPBaseModel): country: Annotated[ - Literal['US'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + Literal['US'], + Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$'), + OmitUnsetDefault, ] = 'US' system: Annotated[ System, Field(description='Country-local postal code system.', title='Postal Code System') @@ -80,7 +82,9 @@ class System1(StrEnum): class PostalArea12(AdCPBaseModel): country: Annotated[ - Literal['GB'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + Literal['GB'], + Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$'), + OmitUnsetDefault, ] = 'GB' system: Annotated[ System1, Field(description='Country-local postal code system.', title='Postal Code System') @@ -117,7 +121,9 @@ class System2(StrEnum): class PostalArea13(AdCPBaseModel): country: Annotated[ - Literal['CA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + Literal['CA'], + Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$'), + OmitUnsetDefault, ] = 'CA' system: Annotated[ System2, Field(description='Country-local postal code system.', title='Postal Code System') @@ -161,56 +167,72 @@ class PostalArea14(AdCPBaseModel): system: Annotated[ Literal['plz'], Field(description='Country-local postal code system.', title='Postal Code System'), + OmitUnsetDefault, ] = 'plz' class PostalArea15(AdCPBaseModel): country: Annotated[ - Literal['FR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + Literal['FR'], + Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$'), + OmitUnsetDefault, ] = 'FR' system: Annotated[ Literal['code_postal'], Field(description='Country-local postal code system.', title='Postal Code System'), + OmitUnsetDefault, ] = 'code_postal' class PostalArea16(AdCPBaseModel): country: Annotated[ - Literal['AU'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + Literal['AU'], + Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$'), + OmitUnsetDefault, ] = 'AU' system: Annotated[ Literal['postcode'], Field(description='Country-local postal code system.', title='Postal Code System'), + OmitUnsetDefault, ] = 'postcode' class PostalArea17(AdCPBaseModel): country: Annotated[ - Literal['BR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + Literal['BR'], + Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$'), + OmitUnsetDefault, ] = 'BR' system: Annotated[ Literal['cep'], Field(description='Country-local postal code system.', title='Postal Code System'), + OmitUnsetDefault, ] = 'cep' class PostalArea18(AdCPBaseModel): country: Annotated[ - Literal['IN'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + Literal['IN'], + Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$'), + OmitUnsetDefault, ] = 'IN' system: Annotated[ Literal['pin'], Field(description='Country-local postal code system.', title='Postal Code System'), + OmitUnsetDefault, ] = 'pin' class PostalArea19(AdCPBaseModel): country: Annotated[ - Literal['ZA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + Literal['ZA'], + Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$'), + OmitUnsetDefault, ] = 'ZA' system: Annotated[ Literal['postal_code'], Field(description='Country-local postal code system.', title='Postal Code System'), + OmitUnsetDefault, ] = 'postal_code' @@ -380,7 +402,7 @@ class PostalArea121(PostalArea110): class PostalArea1( - RootModel[ + AdCPRootModel[ PostalArea112 | PostalArea113 | PostalArea114 @@ -417,7 +439,7 @@ def __getattr__(self, name: str) -> Any: -class PostalArea(RootModel[PostalArea2 | PostalArea1]): +class PostalArea(AdCPRootModel[PostalArea2 | PostalArea1]): root: Annotated[ PostalArea2 | PostalArea1, Field( diff --git a/src/adcp/types/domains/core/postal_country_system.py b/src/adcp/types/domains/core/postal_country_system.py index 289c69f07..9e138e0d1 100644 --- a/src/adcp/types/domains/core/postal_country_system.py +++ b/src/adcp/types/domains/core/postal_country_system.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/postal_country_system.json -# timestamp: 2026-10-02T23:18:44+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel +from adcp.types.base import AdCPBaseModel, OmitUnsetDefault from pydantic import Field @@ -41,7 +41,9 @@ class System(StrEnum): class PostalCountrySystem1(AdCPBaseModel): country: Annotated[ - Literal['US'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + Literal['US'], + Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$'), + OmitUnsetDefault, ] = 'US' system: Annotated[ System, Field(description='Country-local postal code system.', title='Postal Code System') @@ -78,7 +80,9 @@ class System11(StrEnum): class PostalCountrySystem2(AdCPBaseModel): country: Annotated[ - Literal['GB'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + Literal['GB'], + Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$'), + OmitUnsetDefault, ] = 'GB' system: Annotated[ System11, Field(description='Country-local postal code system.', title='Postal Code System') @@ -115,7 +119,9 @@ class System12(StrEnum): class PostalCountrySystem3(AdCPBaseModel): country: Annotated[ - Literal['CA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + Literal['CA'], + Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$'), + OmitUnsetDefault, ] = 'CA' system: Annotated[ System12, Field(description='Country-local postal code system.', title='Postal Code System') @@ -159,56 +165,72 @@ class PostalCountrySystem4(AdCPBaseModel): system: Annotated[ Literal['plz'], Field(description='Country-local postal code system.', title='Postal Code System'), + OmitUnsetDefault, ] = 'plz' class PostalCountrySystem5(AdCPBaseModel): country: Annotated[ - Literal['FR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + Literal['FR'], + Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$'), + OmitUnsetDefault, ] = 'FR' system: Annotated[ Literal['code_postal'], Field(description='Country-local postal code system.', title='Postal Code System'), + OmitUnsetDefault, ] = 'code_postal' class PostalCountrySystem6(AdCPBaseModel): country: Annotated[ - Literal['AU'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + Literal['AU'], + Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$'), + OmitUnsetDefault, ] = 'AU' system: Annotated[ Literal['postcode'], Field(description='Country-local postal code system.', title='Postal Code System'), + OmitUnsetDefault, ] = 'postcode' class PostalCountrySystem7(AdCPBaseModel): country: Annotated[ - Literal['BR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + Literal['BR'], + Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$'), + OmitUnsetDefault, ] = 'BR' system: Annotated[ Literal['cep'], Field(description='Country-local postal code system.', title='Postal Code System'), + OmitUnsetDefault, ] = 'cep' class PostalCountrySystem8(AdCPBaseModel): country: Annotated[ - Literal['IN'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + Literal['IN'], + Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$'), + OmitUnsetDefault, ] = 'IN' system: Annotated[ Literal['pin'], Field(description='Country-local postal code system.', title='Postal Code System'), + OmitUnsetDefault, ] = 'pin' class PostalCountrySystem9(AdCPBaseModel): country: Annotated[ - Literal['ZA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + Literal['ZA'], + Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$'), + OmitUnsetDefault, ] = 'ZA' system: Annotated[ Literal['postal_code'], Field(description='Country-local postal code system.', title='Postal Code System'), + OmitUnsetDefault, ] = 'postal_code' diff --git a/src/adcp/types/domains/core/preview_provider.py b/src/adcp/types/domains/core/preview_provider.py index a0e5c540b..d8910f24a 100644 --- a/src/adcp/types/domains/core/preview_provider.py +++ b/src/adcp/types/domains/core/preview_provider.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/preview_provider.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl from pydantic import ConfigDict, Field, StrictBool, field_validator @@ -34,6 +34,7 @@ class Route(AdCPBaseModel): description="True only when the publisher delegates both creative rendering and the complete placement-specific frame to this route. When false or omitted, consumers compose any presentation_ref around the provider's creative render." ), NoExplicitNull, + OmitUnsetDefault, ] = False diff --git a/src/adcp/types/domains/core/product.py b/src/adcp/types/domains/core/product.py index f3e6a3d34..493532803 100644 --- a/src/adcp/types/domains/core/product.py +++ b/src/adcp/types/domains/core/product.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/product.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Any, Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl, WireDatetime, WireEmail +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl, WireDatetime, WireEmail from adcp.types._scalar import ScalarFloat, ScalarStr from pydantic import ConfigDict, Field, StrictBool, model_validator @@ -359,11 +359,13 @@ class Provider(AdCPBaseModel): StrictBool | None, Field(description='Whether this provider handles context match for this product.'), NoExplicitNull, + OmitUnsetDefault, ] = False identity_match: Annotated[ StrictBool | None, Field(description='Whether this provider handles identity match for this product.'), NoExplicitNull, + OmitUnsetDefault, ] = False countries: Annotated[ list[Country] | None, @@ -399,11 +401,13 @@ class TrustedMatch(AdCPBaseModel): description="Whether this product supports Identity Match requests. When true, the publisher's TMP router will send identity match requests to evaluate user eligibility." ), NoExplicitNull, + OmitUnsetDefault, ] = False response_types: Annotated[ list[response_type.TmpResponseType] | None, Field(description='What the publisher can accept back from context match.', min_length=1), NoExplicitNull, + OmitUnsetDefault, ] = [response_type.TmpResponseType.activation] dynamic_brands: Annotated[ StrictBool | None, @@ -411,6 +415,7 @@ class TrustedMatch(AdCPBaseModel): description="Whether the buyer can select a brand at match time. When false (default), the brand must be specified on the media buy/package. When true, the buyer's offer can include any brand — the publisher applies approval rules at match time. Enables multi-brand agreements where the holding company or buyer agent selects brand based on context." ), NoExplicitNull, + OmitUnsetDefault, ] = False providers: Annotated[ list[Provider] | None, @@ -758,6 +763,7 @@ def _coerce_publisher_property_models(cls, data: Any) -> Any: description="Whether buyers can select a subset of this product's publisher_properties through targeting_overlay.property_list. When false, the product is fixed inventory: it matches requested property targeting only when its inherent property set already satisfies the request, or when a configured product discloses additional inventory through targeting_resolution." ), NoExplicitNull, + OmitUnsetDefault, ] = False data_provider_signals: Annotated[ list[data_provider_signal_selector.DataProviderSignalSelector] | None, @@ -796,6 +802,7 @@ def _coerce_publisher_property_models(cls, data: Any) -> Any: description='Whether this product has a package-level signal_targeting_groups surface. When false (default), signals are bundled into the product terms and cannot be selected or explicitly echoed as package signal groups. When true, eligible signals from inline signal_targeting_options or from get_signals may be buyer-selected or seller-applied according to signal_targeting_rules and are represented through packages[].targeting_overlay.signal_targeting_groups. Editability is controlled by signal_targeting_rules; fixed/default-only products still set this to true when applied signal groups are echoed.' ), NoExplicitNull, + OmitUnsetDefault, ] = False demographic_targeting: Annotated[ demographic_targeting_capability.DemographicTargetingCapability | None, @@ -943,6 +950,7 @@ def _coerce_publisher_property_models(cls, data: Any) -> Any: description="Whether buyers can select a subset of this product's collections through targeting_overlay.collection_list, collection_lists, or collection_selection. When false, the product is a fixed bundle (a collection_selection that exactly restates the complete bundle remains an inherent match); when true, collection selection is a product-scoped overlay capability." ), NoExplicitNull, + OmitUnsetDefault, ] = False list_applications: Annotated[ list[inventory_list_application.InventoryListApplication] | None, diff --git a/src/adcp/types/domains/core/product_audience_evidence_requirements.py b/src/adcp/types/domains/core/product_audience_evidence_requirements.py index 21757d6df..52c3b50d0 100644 --- a/src/adcp/types/domains/core/product_audience_evidence_requirements.py +++ b/src/adcp/types/domains/core/product_audience_evidence_requirements.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/product_audience_evidence_requirements.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..enums import audience_evidence_methodology, audience_resolution_method, audience_subject_type @@ -117,8 +117,12 @@ class ProductAudienceEvidenceRequirements(AdCPBaseModel): ] = None minimum_confidence: Annotated[StrictFloat | None, Field(ge=0.0, le=1.0), NoExplicitNull] = None maximum_age: Annotated[MaximumAge | None, NoExplicitNull] = None - methodology_documentation_required: Annotated[StrictBool | None, NoExplicitNull] = False - independent_attestation_required: Annotated[StrictBool | None, NoExplicitNull] = False + methodology_documentation_required: Annotated[ + StrictBool | None, NoExplicitNull, OmitUnsetDefault + ] = False + independent_attestation_required: Annotated[ + StrictBool | None, NoExplicitNull, OmitUnsetDefault + ] = False accepted_attestation_issuers: Annotated[ list[AcceptedAttestationIssuers] | None, Field(min_length=1), NoExplicitNull ] = None diff --git a/src/adcp/types/domains/core/product_change_map.py b/src/adcp/types/domains/core/product_change_map.py index 1b6619e8b..3ffe8567f 100644 --- a/src/adcp/types/domains/core/product_change_map.py +++ b/src/adcp/types/domains/core/product_change_map.py @@ -1,13 +1,14 @@ # generated by datamodel-codegen: # filename: core/product_change_map.json -# timestamp: 2026-10-03T15:15:43+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from pydantic import Field, RootModel, StringConstraints +from adcp.types.base import AdCPRootModel +from pydantic import Field, StringConstraints class ProductChangeMap1(StrEnum): @@ -15,7 +16,7 @@ class ProductChangeMap1(StrEnum): omit = 'omit' -class ProductChangeMap(RootModel[dict[Annotated[str, StringConstraints(min_length=1)], ProductChangeMap1]]): +class ProductChangeMap(AdCPRootModel[dict[Annotated[str, StringConstraints(min_length=1)], ProductChangeMap1]]): root: Annotated[ dict[Annotated[str, StringConstraints(min_length=1)], ProductChangeMap1], Field( diff --git a/src/adcp/types/domains/core/product_execution_requirement.py b/src/adcp/types/domains/core/product_execution_requirement.py index db7c16bcc..a264274e1 100644 --- a/src/adcp/types/domains/core/product_execution_requirement.py +++ b/src/adcp/types/domains/core/product_execution_requirement.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/product_execution_requirement.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field @@ -71,6 +71,7 @@ class Connection(DownstreamConnectionRequirement): Field( description='Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.' ), + OmitUnsetDefault, ] = 'unknown' required_for: Annotated[ list[RequiredForItem] | None, diff --git a/src/adcp/types/domains/core/product_filters.py b/src/adcp/types/domains/core/product_filters.py index 34c9b8ce9..55a6bb0d2 100644 --- a/src/adcp/types/domains/core/product_filters.py +++ b/src/adcp/types/domains/core/product_filters.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/product_filters.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool, StrictFloat @@ -171,6 +171,7 @@ class SignalTargetingItem4(AdCPBaseModel): description="Desired use of this signal on the product. 'include' requires the product option to allow use in an any group. 'exclude' requires the product option to allow use in a none group." ), NoExplicitNull, + OmitUnsetDefault, ] = TargetingMode.include @@ -200,7 +201,7 @@ class Keyword(AdCPBaseModel): extra='forbid', ) keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] - match_type: Annotated[match_type_1.MatchType | None, NoExplicitNull] = ( + match_type: Annotated[match_type_1.MatchType | None, NoExplicitNull, OmitUnsetDefault] = ( match_type_1.MatchType.broad ) @@ -325,6 +326,7 @@ class SignalTargetingItem5(SignalTargetingItem1, SignalTargetingItem4): description="Desired use of this signal on the product. 'include' requires the product option to allow use in an any group. 'exclude' requires the product option to allow use in a none group." ), NoExplicitNull, + OmitUnsetDefault, ] = TargetingMode.include @@ -335,6 +337,7 @@ class SignalTargetingItem6(SignalTargetingItem2, SignalTargetingItem4): description="Desired use of this signal on the product. 'include' requires the product option to allow use in an any group. 'exclude' requires the product option to allow use in a none group." ), NoExplicitNull, + OmitUnsetDefault, ] = TargetingMode.include @@ -345,6 +348,7 @@ class SignalTargetingItem7(SignalTargetingItem3, SignalTargetingItem4): description="Desired use of this signal on the product. 'include' requires the product option to allow use in an any group. 'exclude' requires the product option to allow use in a none group." ), NoExplicitNull, + OmitUnsetDefault, ] = TargetingMode.include diff --git a/src/adcp/types/domains/core/product_format_declaration.py b/src/adcp/types/domains/core/product_format_declaration.py index 78f25e011..2cd20890d 100644 --- a/src/adcp/types/domains/core/product_format_declaration.py +++ b/src/adcp/types/domains/core/product_format_declaration.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/product_format_declaration.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl from pydantic import Field, StrictBool from ..enums import channels @@ -117,6 +117,7 @@ class ProductFormatDeclaration1(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -124,6 +125,7 @@ class ProductFormatDeclaration1(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -230,6 +232,7 @@ class ProductFormatDeclaration2(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -237,6 +240,7 @@ class ProductFormatDeclaration2(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -343,6 +347,7 @@ class ProductFormatDeclaration3(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -350,6 +355,7 @@ class ProductFormatDeclaration3(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -456,6 +462,7 @@ class ProductFormatDeclaration4(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -463,6 +470,7 @@ class ProductFormatDeclaration4(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -569,6 +577,7 @@ class ProductFormatDeclaration5(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -576,6 +585,7 @@ class ProductFormatDeclaration5(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -682,6 +692,7 @@ class ProductFormatDeclaration6(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -689,6 +700,7 @@ class ProductFormatDeclaration6(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -795,6 +807,7 @@ class ProductFormatDeclaration7(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -802,6 +815,7 @@ class ProductFormatDeclaration7(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -908,6 +922,7 @@ class ProductFormatDeclaration8(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -915,6 +930,7 @@ class ProductFormatDeclaration8(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1021,6 +1037,7 @@ class ProductFormatDeclaration9(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1028,6 +1045,7 @@ class ProductFormatDeclaration9(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1134,6 +1152,7 @@ class ProductFormatDeclaration10(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1141,6 +1160,7 @@ class ProductFormatDeclaration10(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1247,6 +1267,7 @@ class ProductFormatDeclaration11(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1254,6 +1275,7 @@ class ProductFormatDeclaration11(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1360,6 +1382,7 @@ class ProductFormatDeclaration12(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1367,6 +1390,7 @@ class ProductFormatDeclaration12(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1473,6 +1497,7 @@ class ProductFormatDeclaration13(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1480,6 +1505,7 @@ class ProductFormatDeclaration13(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1586,6 +1612,7 @@ class ProductFormatDeclaration14(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1593,6 +1620,7 @@ class ProductFormatDeclaration14(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1699,6 +1727,7 @@ class ProductFormatDeclaration15(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1706,6 +1735,7 @@ class ProductFormatDeclaration15(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1812,6 +1842,7 @@ class ProductFormatDeclaration16(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1819,6 +1850,7 @@ class ProductFormatDeclaration16(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, diff --git a/src/adcp/types/domains/core/product_signal_targeting_option.py b/src/adcp/types/domains/core/product_signal_targeting_option.py index ca76f3938..eff99f5ca 100644 --- a/src/adcp/types/domains/core/product_signal_targeting_option.py +++ b/src/adcp/types/domains/core/product_signal_targeting_option.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/product_signal_targeting_option.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from . import signal_ref, vendor_pricing_option @@ -41,6 +41,7 @@ class ProductSignalTargetingOption(SignalListing): description="Whether this signal option is ready to select on create_media_buy for the requesting account. 'ready' means the buyer can select it directly. 'requires_activation' means the buyer must activate the signal first or include an activation_key the seller accepts." ), NoExplicitNull, + OmitUnsetDefault, ] = ActivationStatus.ready allowed_targeting_modes: Annotated[ list[AllowedTargetingMode] | None, @@ -49,6 +50,7 @@ class ProductSignalTargetingOption(SignalListing): min_length=1, ), NoExplicitNull, + OmitUnsetDefault, ] = [AllowedTargetingMode.include] default_selected: Annotated[ StrictBool | None, @@ -56,6 +58,7 @@ class ProductSignalTargetingOption(SignalListing): description="Whether the seller recommends or preselects this signal when composing this product. Buyers may remove it unless signal_targeting_rules.selection_mode is 'fixed'. When selection_mode is 'fixed', sellers apply default_selected signals even if the buyer omits signal_targeting_groups and MUST echo the applied entries on the resulting package state." ), NoExplicitNull, + OmitUnsetDefault, ] = False selection_group: Annotated[ str | None, diff --git a/src/adcp/types/domains/core/protocol_envelope.py b/src/adcp/types/domains/core/protocol_envelope.py index 5369da89c..5c614fbe1 100644 --- a/src/adcp/types/domains/core/protocol_envelope.py +++ b/src/adcp/types/domains/core/protocol_envelope.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/protocol_envelope.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from pydantic import ConfigDict, Field, StrictBool from ..enums import task_status @@ -66,6 +66,7 @@ class ProtocolEnvelope(AdCPBaseModel): description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." ), NoExplicitNull, + OmitUnsetDefault, ] = False adcp_error: Annotated[ error.Error | None, diff --git a/src/adcp/types/domains/core/registry_event.py b/src/adcp/types/domains/core/registry_event.py index 050c93487..07fbb8fa4 100644 --- a/src/adcp/types/domains/core/registry_event.py +++ b/src/adcp/types/domains/core/registry_event.py @@ -1,15 +1,15 @@ # generated by datamodel-codegen: # filename: core/registry_event.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl, WireDatetime, WireUuid +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl, WireDatetime, WireUuid from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel, StrictBool +from pydantic import ConfigDict, Field, StrictBool from ..enums import adcp_protocol, collection_kind, distribution_identifier_type from . import agent_signing_key @@ -81,7 +81,9 @@ class RegistryEvent3(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'property.merged' entity_type: Annotated[ - Literal['property'], Field(description='Entity class touched by this event.') + Literal['property'], + Field(description='Entity class touched by this event.'), + OmitUnsetDefault, ] = 'property' entity_id: Annotated[ str, @@ -130,7 +132,9 @@ class RegistryEvent4(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'property.stale' entity_type: Annotated[ - Literal['property'], Field(description='Entity class touched by this event.') + Literal['property'], + Field(description='Entity class touched by this event.'), + OmitUnsetDefault, ] = 'property' entity_id: Annotated[ str, @@ -189,7 +193,9 @@ class RegistryEvent8(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'collection.merged' entity_type: Annotated[ - Literal['collection'], Field(description='Entity class touched by this event.') + Literal['collection'], + Field(description='Entity class touched by this event.'), + OmitUnsetDefault, ] = 'collection' entity_id: Annotated[ str, @@ -268,15 +274,15 @@ class Domain(ScalarStr): } -class BadgeRole(RootModel[adcp_protocol.AdcpProtocol]): +class BadgeRole(AdCPRootModel[adcp_protocol.AdcpProtocol]): root: adcp_protocol.AdcpProtocol -class StringArray(RootModel[list[str]]): +class StringArray(AdCPRootModel[list[str]]): root: list[str] -class ChangedFields(RootModel[list[str]]): +class ChangedFields(AdCPRootModel[list[str]]): root: Annotated[ list[str], Field( @@ -359,7 +365,7 @@ class Country(Market): pass -class Countries(RootModel[list[Country]]): +class Countries(AdCPRootModel[list[Country]]): root: Annotated[list[Country], Field(min_length=1)] @@ -392,7 +398,7 @@ class RegistryEvent10(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'agent.discovered' entity_type: Annotated[ - Literal['agent'], Field(description='Entity class touched by this event.') + Literal['agent'], Field(description='Entity class touched by this event.'), OmitUnsetDefault ] = 'agent' entity_id: Annotated[ str, @@ -442,7 +448,7 @@ class RegistryEvent11(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'agent.removed' entity_type: Annotated[ - Literal['agent'], Field(description='Entity class touched by this event.') + Literal['agent'], Field(description='Entity class touched by this event.'), OmitUnsetDefault ] = 'agent' entity_id: Annotated[ str, @@ -487,7 +493,7 @@ class RegistryEvent12(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'agent.profile_updated' entity_type: Annotated[ - Literal['agent'], Field(description='Entity class touched by this event.') + Literal['agent'], Field(description='Entity class touched by this event.'), OmitUnsetDefault ] = 'agent' entity_id: Annotated[ str, @@ -553,7 +559,7 @@ class RegistryEvent13(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'agent.compliance_changed' entity_type: Annotated[ - Literal['agent'], Field(description='Entity class touched by this event.') + Literal['agent'], Field(description='Entity class touched by this event.'), OmitUnsetDefault ] = 'agent' entity_id: Annotated[ str, @@ -610,7 +616,7 @@ class RegistryEvent14(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'agent.verification_earned' entity_type: Annotated[ - Literal['agent'], Field(description='Entity class touched by this event.') + Literal['agent'], Field(description='Entity class touched by this event.'), OmitUnsetDefault ] = 'agent' entity_id: Annotated[ str, @@ -667,7 +673,7 @@ class RegistryEvent15(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'agent.verification_lost' entity_type: Annotated[ - Literal['agent'], Field(description='Entity class touched by this event.') + Literal['agent'], Field(description='Entity class touched by this event.'), OmitUnsetDefault ] = 'agent' entity_id: Annotated[ str, @@ -745,7 +751,9 @@ class RegistryEvent16(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'publisher.adagents_discovered' entity_type: Annotated[ - Literal['publisher'], Field(description='Entity class touched by this event.') + Literal['publisher'], + Field(description='Entity class touched by this event.'), + OmitUnsetDefault, ] = 'publisher' entity_id: Annotated[ str, @@ -870,7 +878,9 @@ class RegistryEvent18(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'authorization.granted' entity_type: Annotated[ - Literal['authorization'], Field(description='Entity class touched by this event.') + Literal['authorization'], + Field(description='Entity class touched by this event.'), + OmitUnsetDefault, ] = 'authorization' entity_id: Annotated[ str, @@ -914,7 +924,9 @@ class RegistryEvent19(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'authorization.revoked' entity_type: Annotated[ - Literal['authorization'], Field(description='Entity class touched by this event.') + Literal['authorization'], + Field(description='Entity class touched by this event.'), + OmitUnsetDefault, ] = 'authorization' entity_id: Annotated[ str, @@ -958,7 +970,9 @@ class RegistryEvent20(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'authorization.modified' entity_type: Annotated[ - Literal['authorization'], Field(description='Entity class touched by this event.') + Literal['authorization'], + Field(description='Entity class touched by this event.'), + OmitUnsetDefault, ] = 'authorization' entity_id: Annotated[ str, @@ -1029,7 +1043,9 @@ class RegistryEvent1(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'property.created' entity_type: Annotated[ - Literal['property'], Field(description='Entity class touched by this event.') + Literal['property'], + Field(description='Entity class touched by this event.'), + OmitUnsetDefault, ] = 'property' entity_id: Annotated[ str, @@ -1069,7 +1085,9 @@ class RegistryEvent2(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'property.updated' entity_type: Annotated[ - Literal['property'], Field(description='Entity class touched by this event.') + Literal['property'], + Field(description='Entity class touched by this event.'), + OmitUnsetDefault, ] = 'property' entity_id: Annotated[ str, @@ -1114,7 +1132,9 @@ class RegistryEvent5(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'property.reactivated' entity_type: Annotated[ - Literal['property'], Field(description='Entity class touched by this event.') + Literal['property'], + Field(description='Entity class touched by this event.'), + OmitUnsetDefault, ] = 'property' entity_id: Annotated[ str, @@ -1162,7 +1182,9 @@ class RegistryEvent17(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'publisher.adagents_changed' entity_type: Annotated[ - Literal['publisher'], Field(description='Entity class touched by this event.') + Literal['publisher'], + Field(description='Entity class touched by this event.'), + OmitUnsetDefault, ] = 'publisher' entity_id: Annotated[ str, @@ -1229,7 +1251,9 @@ class RegistryEvent6(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'collection.created' entity_type: Annotated[ - Literal['collection'], Field(description='Entity class touched by this event.') + Literal['collection'], + Field(description='Entity class touched by this event.'), + OmitUnsetDefault, ] = 'collection' entity_id: Annotated[ str, @@ -1269,7 +1293,9 @@ class RegistryEvent7(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'collection.updated' entity_type: Annotated[ - Literal['collection'], Field(description='Entity class touched by this event.') + Literal['collection'], + Field(description='Entity class touched by this event.'), + OmitUnsetDefault, ] = 'collection' entity_id: Annotated[ str, @@ -1315,7 +1341,9 @@ class RegistryEvent9(AdCPBaseModel): Field(description='Discriminator. Determines the shape of payload.'), ] = 'collection.removed' entity_type: Annotated[ - Literal['collection'], Field(description='Entity class touched by this event.') + Literal['collection'], + Field(description='Entity class touched by this event.'), + OmitUnsetDefault, ] = 'collection' entity_id: Annotated[ str, diff --git a/src/adcp/types/domains/core/reporting_delivery_capabilities.py b/src/adcp/types/domains/core/reporting_delivery_capabilities.py index 328633ff9..0d2a194fe 100644 --- a/src/adcp/types/domains/core/reporting_delivery_capabilities.py +++ b/src/adcp/types/domains/core/reporting_delivery_capabilities.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/reporting_delivery_capabilities.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl, WireEmail +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl, WireEmail from pydantic import ConfigDict, Field, StrictBool from . import reporting_delivery_offering, reporting_reliability_statistics @@ -47,6 +47,7 @@ class ReportingDeliveryCapabilities(AdCPBaseModel): Field( description='Explicit adoption declaration for the proper-name AdCP 3.2 Reliable Reporting contract. Presence, together with supported: true and the media_buy.reporting_delivery experimental feature gate, is the affirmative machine-readable answer. Absence denotes the earlier experimental managed-reporting shape.' ), + OmitUnsetDefault, ] = None managed_delivery: Annotated[ StrictBool | None, @@ -54,6 +55,7 @@ class ReportingDeliveryCapabilities(AdCPBaseModel): description='Tier flag: this seller supports managed file, dataset-share, or warehouse delivery. Offerings whose method names a delivery pattern require this tier. When false or absent, every offering is API-delivered and Core-only.' ), NoExplicitNull, + OmitUnsetDefault, ] = None reconciled_billing: Annotated[ StrictBool | None, @@ -61,6 +63,7 @@ class ReportingDeliveryCapabilities(AdCPBaseModel): description='Tier flag: this seller supports canonical-digest verification and authenticated consumer receipts for both report materializations and post-official adjustments through receipt_task. Offerings with reconciliation_mode consumer_receipt and billing-grade canonicalization require this tier.' ), NoExplicitNull, + OmitUnsetDefault, ] = None configuration_task: Literal['sync_accounts'] | None = None status_task: Literal['get_reporting_status'] | None = None @@ -69,36 +72,42 @@ class ReportingDeliveryCapabilities(AdCPBaseModel): Field( description='Opt-in consumer-status loop during the published migration window, becoming required Core in the next eligible minor after that window. Buyers call this seller-hosted task to record whether each expected reporting period was received, missing, or unreadable. Buyers expose no reverse endpoint, and the status is not a billing receipt.' ), + OmitUnsetDefault, ] = None revision_content_task: Annotated[ Literal['get_media_buy_delivery'] | None, Field( description='Reliable Reporting exact-content read: callers select reporting_revision_id and receive immutable revision metadata plus authoritative canonical reporting_rows.' ), + OmitUnsetDefault, ] = None receipt_task: Annotated[ Literal['sync_reporting_receipts'] | None, Field( description='Required when reconciled_billing is true: the task consumers call to submit and read back authenticated revision and adjustment receipts.' ), + OmitUnsetDefault, ] = None readiness_notification: Annotated[ Literal['reporting.delivery_ready'] | None, Field( description='Optional managed-delivery-only positive-readiness doorbell. It names a materialization at a destination, so Core sellers MUST omit it.' ), + OmitUnsetDefault, ] = None status_notification: Annotated[ Literal['reporting.status_changed'] | None, Field( description='Optional tier-independent invalidation doorbell for health transitions in either direction, including clock-driven waiting-to-delayed and delayed-to-action_required. Valid for Core: it names no destination. Polling status_task remains the authoritative recovery path whether or not this is offered.' ), + OmitUnsetDefault, ] = None ledger_notification: Annotated[ Literal['reporting.ledger_changed'] | None, Field( description='Optional tier-independent invalidation for every newly committed revision or post-official adjustment, even when health does not change. Receivers repair through get_reporting_status changes_after; polling remains authoritative.' ), + OmitUnsetDefault, ] = None offerings: Annotated[ list[reporting_delivery_offering.ReportingDeliveryOffering], @@ -151,7 +160,9 @@ class ReportingDeliveryCapabilities(AdCPBaseModel): ), NoExplicitNull, ] = None - supports_webhook_activity: Annotated[StrictBool | None, NoExplicitNull] = None + supports_webhook_activity: Annotated[StrictBool | None, NoExplicitNull, OmitUnsetDefault] = ( + None + ) authorization_revocation_seconds: Annotated[ SchemaInt | None, Field( diff --git a/src/adcp/types/domains/core/reporting_delivery_config.py b/src/adcp/types/domains/core/reporting_delivery_config.py index 82692fdee..c9b535145 100644 --- a/src/adcp/types/domains/core/reporting_delivery_config.py +++ b/src/adcp/types/domains/core/reporting_delivery_config.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/reporting_delivery_config.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -11,7 +11,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireDatetime from pydantic import ConfigDict, Field, StrictBool from ..enums import reporting_finality @@ -28,7 +28,7 @@ class Scope(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) - all_media_buys: Annotated[Literal[True] | None, NoExplicitNull] = None + all_media_buys: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None media_buy_ids: Annotated[ list[reporting_coverage.ReportingMediaBuyId] | None, Field(min_length=1), NoExplicitNull ] = None @@ -154,6 +154,7 @@ class ReportingDeliveryConfiguration(AdCPBaseModel): description="Reserved: which party's count of this feed is authoritative. seller (the default, and the only value any 3.2 seller accepts) means the seller produces every revision and the consumer may only attest to what it consumed. consumer is reserved for the buyer-deposited billing revision task scoped to a later minor; until that task exists sellers MUST reject it with UNSUPPORTED_FEATURE. Reserving the field now keeps a future buyer-basis billing feed additive instead of breaking the billing-feed constraints. See https://github.com/adcontextprotocol/adcp/issues/7440." ), NoExplicitNull, + OmitUnsetDefault, ] = AuthoritativeParty.seller schedule: reporting_schedule.ReportingSchedule method: Annotated[reporting_delivery_method.ReportingDeliveryMethod | None, NoExplicitNull] = ( diff --git a/src/adcp/types/domains/core/reporting_delivery_offering.py b/src/adcp/types/domains/core/reporting_delivery_offering.py index 6b04cd66d..2feb2a9fd 100644 --- a/src/adcp/types/domains/core/reporting_delivery_offering.py +++ b/src/adcp/types/domains/core/reporting_delivery_offering.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/reporting_delivery_offering.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl from pydantic import ConfigDict, Field from ..enums import reporting_finality @@ -73,10 +73,10 @@ class ReportingProfile(AdCPBaseModel): ), NoExplicitNull, ] = None - canonicalization_contract_version: Literal['1.0'] = '1.0' - canonicalization_media_type: Literal['application/vnd.adcp.reporting-canonicalization+json'] = ( - 'application/vnd.adcp.reporting-canonicalization+json' - ) + canonicalization_contract_version: Annotated[Literal['1.0'], OmitUnsetDefault] = '1.0' + canonicalization_media_type: Annotated[ + Literal['application/vnd.adcp.reporting-canonicalization+json'], OmitUnsetDefault + ] = 'application/vnd.adcp.reporting-canonicalization+json' canonicalization_uri: Annotated[ WireAnyUrl | None, Field( diff --git a/src/adcp/types/domains/core/reporting_report_definition.py b/src/adcp/types/domains/core/reporting_report_definition.py index b19eab70e..ccc8bed1f 100644 --- a/src/adcp/types/domains/core/reporting_report_definition.py +++ b/src/adcp/types/domains/core/reporting_report_definition.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/reporting_report_definition.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field @@ -93,6 +93,7 @@ class RestatementPolicy(AdCPBaseModel): Field( description='Once an official revision is published it is immutable and cannot be superseded. Later source corrections are separate reporting-adjustment records applied to a later accounting period.' ), + OmitUnsetDefault, ] = 'adjustments_only' diff --git a/src/adcp/types/domains/core/reporting_resource.py b/src/adcp/types/domains/core/reporting_resource.py index 02548a36a..48b78a138 100644 --- a/src/adcp/types/domains/core/reporting_resource.py +++ b/src/adcp/types/domains/core/reporting_resource.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/reporting_resource.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field @@ -61,6 +61,7 @@ class ReportingResource(AdCPBaseModel): manifest_version: Annotated[ Literal['1.0'], Field(description='Version of reporting-file-manifest.json used by a manifest resource.'), + OmitUnsetDefault, ] = '1.0' manifest_sha256: Annotated[ str | None, diff --git a/src/adcp/types/domains/core/reporting_verification_profile_set.py b/src/adcp/types/domains/core/reporting_verification_profile_set.py index 8b6ec2b01..e8c293c04 100644 --- a/src/adcp/types/domains/core/reporting_verification_profile_set.py +++ b/src/adcp/types/domains/core/reporting_verification_profile_set.py @@ -1,13 +1,14 @@ # generated by datamodel-codegen: # filename: core/reporting_verification_profile_set.json -# timestamp: 2026-09-01T19:37:00+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from pydantic import Field, RootModel +from adcp.types.base import AdCPRootModel +from pydantic import Field class ReportingVerificationProfileSetEnum(StrEnum): @@ -16,7 +17,7 @@ class ReportingVerificationProfileSetEnum(StrEnum): canonical_digest = 'canonical_digest' -class ReportingVerificationProfileSet(RootModel[list[ReportingVerificationProfileSetEnum]]): +class ReportingVerificationProfileSet(AdCPRootModel[list[ReportingVerificationProfileSetEnum]]): root: Annotated[ list[ReportingVerificationProfileSetEnum], Field( diff --git a/src/adcp/types/domains/core/requirements/catalog_requirements.py b/src/adcp/types/domains/core/requirements/catalog_requirements.py index 86505c335..b056ea752 100644 --- a/src/adcp/types/domains/core/requirements/catalog_requirements.py +++ b/src/adcp/types/domains/core/requirements/catalog_requirements.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/requirements/catalog_requirements.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictBool from ...enums import catalog_type as catalog_type_1 @@ -28,6 +28,7 @@ class CatalogRequirements(AdCPBaseModel): description='Whether this catalog type must be present. When true, creatives using this format must reference a synced catalog of this type.' ), NoExplicitNull, + OmitUnsetDefault, ] = True min_items: Annotated[ SchemaInt | None, diff --git a/src/adcp/types/domains/core/requirements/html_asset_requirements.py b/src/adcp/types/domains/core/requirements/html_asset_requirements.py index b2fd842fd..670248dcb 100644 --- a/src/adcp/types/domains/core/requirements/html_asset_requirements.py +++ b/src/adcp/types/domains/core/requirements/html_asset_requirements.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/requirements/html_asset_requirements.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictBool @@ -33,6 +33,7 @@ class HtmlAssetRequirements(AdCPBaseModel): description="Sandbox environment the HTML must be compatible with. 'none' = direct DOM access, 'iframe' = standard iframe isolation, 'safeframe' = IAB SafeFrame container, 'fencedframe' = Privacy Sandbox fenced frame" ), NoExplicitNull, + OmitUnsetDefault, ] = Sandbox.none external_resources_allowed: Annotated[ StrictBool | None, diff --git a/src/adcp/types/domains/core/requirements/offering_asset_constraint.py b/src/adcp/types/domains/core/requirements/offering_asset_constraint.py index f9392afeb..21bc2693e 100644 --- a/src/adcp/types/domains/core/requirements/offering_asset_constraint.py +++ b/src/adcp/types/domains/core/requirements/offering_asset_constraint.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/requirements/offering_asset_constraint.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictBool from ...enums import asset_content_type @@ -34,6 +34,7 @@ class OfferingAssetConstraint(AdCPBaseModel): description='Whether this asset group must be present in each offering. Defaults to true.' ), NoExplicitNull, + OmitUnsetDefault, ] = True min_count: Annotated[ SchemaInt | None, diff --git a/src/adcp/types/domains/core/rights_attestation_evaluation.py b/src/adcp/types/domains/core/rights_attestation_evaluation.py index 0ed4445ff..fa5186ccd 100644 --- a/src/adcp/types/domains/core/rights_attestation_evaluation.py +++ b/src/adcp/types/domains/core/rights_attestation_evaluation.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/rights_attestation_evaluation.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl from pydantic import ConfigDict, Field from . import brand_ref @@ -182,6 +182,7 @@ class Reference(AttestationReference): Field( description='Open, absolute URI identifying the claim vocabulary. The URI is an identifier and need not be dereferenceable. AdCP does not maintain an enum of approved claims.' ), + OmitUnsetDefault, ] = 'https://adcontextprotocol.org/claims/rights/grant' subject: Annotated[ Subject10 | Subject | Subject19 | None, diff --git a/src/adcp/types/domains/core/rights_constraint.py b/src/adcp/types/domains/core/rights_constraint.py index 9f10051a0..b8e05d83c 100644 --- a/src/adcp/types/domains/core/rights_constraint.py +++ b/src/adcp/types/domains/core/rights_constraint.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/rights_constraint.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl, WireDatetime from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool @@ -189,6 +189,7 @@ class AttestationRef(AttestationReference): Field( description='Open, absolute URI identifying the claim vocabulary. The URI is an identifier and need not be dereferenceable. AdCP does not maintain an enum of approved claims.' ), + OmitUnsetDefault, ] = 'https://adcontextprotocol.org/claims/rights/grant' subject: Annotated[ Subject20 | Subject | Subject29 | None, diff --git a/src/adcp/types/domains/core/signal_pricing_option.py b/src/adcp/types/domains/core/signal_pricing_option.py index d42f10c56..87f386b30 100644 --- a/src/adcp/types/domains/core/signal_pricing_option.py +++ b/src/adcp/types/domains/core/signal_pricing_option.py @@ -1,17 +1,18 @@ # generated by datamodel-codegen: # filename: core/signal_pricing_option.json -# timestamp: 2026-05-02T19:36:29+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from pydantic import Field, RootModel +from adcp.types.base import AdCPRootModel +from pydantic import Field from . import vendor_pricing_option -class SignalPricingOption(RootModel[vendor_pricing_option.VendorPricingOption]): +class SignalPricingOption(AdCPRootModel[vendor_pricing_option.VendorPricingOption]): root: Annotated[ vendor_pricing_option.VendorPricingOption, Field( diff --git a/src/adcp/types/domains/core/signal_targeting_rules.py b/src/adcp/types/domains/core/signal_targeting_rules.py index ce06453e9..a4504f3c4 100644 --- a/src/adcp/types/domains/core/signal_targeting_rules.py +++ b/src/adcp/types/domains/core/signal_targeting_rules.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/signal_targeting_rules.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field from . import signal_selection_group_rule @@ -34,6 +34,7 @@ class SignalTargetingRules(AdCPBaseModel): description="How selected signal_targeting_options are resolved against the product's inventory. 'direct_targeting' means selected signals are applied as targeting predicates to the package inventory. 'seller_planned' means selected signals are planning inputs that the seller resolves against product-specific inventory, timing, availability, reach, or pacing constraints; buyers SHOULD NOT attempt to decompose the signal selection into lower-level inventory or schedule decisions. Use 'seller_planned' for products such as linear broadcast schedules where the audience definition may be portable but the audience-to-avails plan is seller-resolved." ), NoExplicitNull, + OmitUnsetDefault, ] = ResolutionModel.direct_targeting selection_mode: Annotated[ SelectionMode | None, @@ -41,6 +42,7 @@ class SignalTargetingRules(AdCPBaseModel): description="Default selection behavior for selectable signals on this product. 'optional' means the buyer may select zero or more signals. 'required' means the buyer must select at least min_selected_signals, or 1 when min_selected_signals is omitted. 'fixed' means the seller applies the default_selected signals and the buyer cannot add or remove them; buyers SHOULD render those entries as read-only and sellers MUST echo them in package targeting_overlay.signal_targeting_groups. Use selection_group_rules for product-scoped products that need different behavior for different groups, such as fixed suppressions plus a required include tier." ), NoExplicitNull, + OmitUnsetDefault, ] = SelectionMode.optional min_selected_signals: Annotated[ SchemaInt | None, diff --git a/src/adcp/types/domains/core/targeting.py b/src/adcp/types/domains/core/targeting.py index c4a0d3163..489cf502c 100644 --- a/src/adcp/types/domains/core/targeting.py +++ b/src/adcp/types/domains/core/targeting.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/targeting.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from collections.abc import Sequence from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool, StrictFloat @@ -81,6 +81,7 @@ class AgeRestriction(AdCPBaseModel): StrictBool | None, Field(description='Whether verified age (not inferred) is required for compliance'), NoExplicitNull, + OmitUnsetDefault, ] = False accepted_methods: Annotated[ list[age_verification_method.AgeVerificationMethod] | None, diff --git a/src/adcp/types/domains/core/targeting_overlay_requirements.py b/src/adcp/types/domains/core/targeting_overlay_requirements.py index 3ec96fe74..0041b8cbd 100644 --- a/src/adcp/types/domains/core/targeting_overlay_requirements.py +++ b/src/adcp/types/domains/core/targeting_overlay_requirements.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/targeting_overlay_requirements.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Any, Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull -from pydantic import ConfigDict, Field, RootModel +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull +from pydantic import ConfigDict, Field from ..enums import ( browser_family, @@ -27,7 +27,7 @@ ) -class Required(RootModel[Literal[True]]): +class Required(AdCPRootModel[Literal[True]]): root: Literal[True] @@ -38,7 +38,7 @@ class MetroRequirement1(AdCPBaseModel): systems: Annotated[list[metro_system.MetroAreaSystem], Field(min_length=1)] -class MetroRequirement(RootModel[Required | MetroRequirement1]): +class MetroRequirement(AdCPRootModel[Required | MetroRequirement1]): root: Required | MetroRequirement1 def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" @@ -55,7 +55,7 @@ class KeywordRequirement1(AdCPBaseModel): supported_match_types: Annotated[list[match_type.MatchType], Field(min_length=1)] -class KeywordRequirement(RootModel[Required | KeywordRequirement1]): +class KeywordRequirement(AdCPRootModel[Required | KeywordRequirement1]): root: Required | KeywordRequirement1 def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" @@ -72,7 +72,7 @@ class BrowserRequirement1(AdCPBaseModel): families: Annotated[list[browser_family.BrowserFamily], Field(min_length=1)] -class BrowserRequirement(RootModel[Required | BrowserRequirement1]): +class BrowserRequirement(AdCPRootModel[Required | BrowserRequirement1]): root: Required | BrowserRequirement1 def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" @@ -106,7 +106,7 @@ class DaypartRequirement1(AdCPBaseModel): ] = None -class DaypartRequirement(RootModel[Required | DaypartRequirement1]): +class DaypartRequirement(AdCPRootModel[Required | DaypartRequirement1]): root: Required | DaypartRequirement1 def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" diff --git a/src/adcp/types/domains/core/targeting_overlay_support.py b/src/adcp/types/domains/core/targeting_overlay_support.py index a0c0cd5cf..05e8edd94 100644 --- a/src/adcp/types/domains/core/targeting_overlay_support.py +++ b/src/adcp/types/domains/core/targeting_overlay_support.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/targeting_overlay_support.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Any, Annotated, Literal -from adcp.types.base import AdCPBaseModel, GeoPlaceSystemKey, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, AdCPRootModel, GeoPlaceSystemKey, NoExplicitNull, SchemaInt from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel, StringConstraints +from pydantic import ConfigDict, Field, StringConstraints from ..enums import ( browser_family, @@ -38,7 +38,7 @@ class PlacementSelection(AdCPBaseModel): ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None -class Supported(RootModel[Literal[True]]): +class Supported(AdCPRootModel[Literal[True]]): root: Literal[True] @@ -56,7 +56,7 @@ class CountrySupport1(AdCPBaseModel): ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None -class CountrySupport(RootModel[Supported | CountrySupport1]): +class CountrySupport(AdCPRootModel[Supported | CountrySupport1]): root: Supported | CountrySupport1 def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" @@ -83,7 +83,7 @@ class MetroSupport1(AdCPBaseModel): ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None -class MetroSupport(RootModel[Supported | MetroSupport1]): +class MetroSupport(AdCPRootModel[Supported | MetroSupport1]): root: Supported | MetroSupport1 def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" @@ -156,7 +156,7 @@ class CollectionListsSupport1(AdCPBaseModel): ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None -class CollectionListsSupport(RootModel[Supported | CollectionListsSupport1]): +class CollectionListsSupport(AdCPRootModel[Supported | CollectionListsSupport1]): root: Supported | CollectionListsSupport1 def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" @@ -174,7 +174,7 @@ class KeywordSupport1(AdCPBaseModel): ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None -class KeywordSupport(RootModel[Supported | KeywordSupport1]): +class KeywordSupport(AdCPRootModel[Supported | KeywordSupport1]): root: Supported | KeywordSupport1 def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" @@ -192,7 +192,7 @@ class BrowserSupport1(AdCPBaseModel): ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None -class BrowserSupport(RootModel[Supported | BrowserSupport1]): +class BrowserSupport(AdCPRootModel[Supported | BrowserSupport1]): root: Supported | BrowserSupport1 def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" @@ -202,7 +202,7 @@ def __getattr__(self, name: str) -> Any: -class IanaTimezones(RootModel[list[iana_timezone.IanaTimezoneIdentifier]]): +class IanaTimezones(AdCPRootModel[list[iana_timezone.IanaTimezoneIdentifier]]): root: Annotated[ list[iana_timezone.IanaTimezoneIdentifier], Field( @@ -234,7 +234,7 @@ class DaypartSupport1(AdCPBaseModel): ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None -class DaypartSupport(RootModel[Literal[True] | DaypartSupport1]): +class DaypartSupport(AdCPRootModel[Literal[True] | DaypartSupport1]): root: Literal[True] | DaypartSupport1 def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" diff --git a/src/adcp/types/domains/core/targeting_unknown_age_eligibility_constraint.py b/src/adcp/types/domains/core/targeting_unknown_age_eligibility_constraint.py index b28b93d9e..76282c51a 100644 --- a/src/adcp/types/domains/core/targeting_unknown_age_eligibility_constraint.py +++ b/src/adcp/types/domains/core/targeting_unknown_age_eligibility_constraint.py @@ -1,15 +1,16 @@ # generated by datamodel-codegen: # filename: core/targeting_unknown_age_eligibility_constraint.json -# timestamp: 2026-09-14T14:16:02+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Any -from pydantic import Field, RootModel +from adcp.types.base import AdCPRootModel +from pydantic import Field -class TargetingUnknownAgeEligibilityConstraint(RootModel[Any]): +class TargetingUnknownAgeEligibilityConstraint(AdCPRootModel[Any]): root: Annotated[ Any, Field( diff --git a/src/adcp/types/domains/core/targeting_verified_age_basis_constraint.py b/src/adcp/types/domains/core/targeting_verified_age_basis_constraint.py index 61a615b76..945108b24 100644 --- a/src/adcp/types/domains/core/targeting_verified_age_basis_constraint.py +++ b/src/adcp/types/domains/core/targeting_verified_age_basis_constraint.py @@ -1,15 +1,16 @@ # generated by datamodel-codegen: # filename: core/targeting_verified_age_basis_constraint.json -# timestamp: 2026-09-14T14:16:02+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Any -from pydantic import Field, RootModel +from adcp.types.base import AdCPRootModel +from pydantic import Field -class TargetingVerifiedAgeBasisConstraint(RootModel[Any]): +class TargetingVerifiedAgeBasisConstraint(AdCPRootModel[Any]): root: Annotated[ Any, Field( diff --git a/src/adcp/types/domains/core/tasks_get_request.py b/src/adcp/types/domains/core/tasks_get_request.py index bc0046fb3..fd72ed46e 100644 --- a/src/adcp/types/domains/core/tasks_get_request.py +++ b/src/adcp/types/domains/core/tasks_get_request.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/tasks_get_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from . import account_ref @@ -33,6 +33,7 @@ class TasksGetRequest(AdcpVersionEnvelope): description='Include full conversation history for this task (may increase response size)' ), NoExplicitNull, + OmitUnsetDefault, ] = False include_result: Annotated[ StrictBool | None, @@ -40,6 +41,7 @@ class TasksGetRequest(AdcpVersionEnvelope): description="Include the task's canonical terminal result payload when one exists. Defaults to false for lightweight status-only polls. When true, sellers MUST include result for completed, failed, or rejected terminal tasks when that task produced a terminal artifact; canceled tasks may have no result. The legacy singular error field remains a convenience for failed tasks but does not replace the canonical terminal result." ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/core/tasks_list_request.py b/src/adcp/types/domains/core/tasks_list_request.py index 295ea24b7..1a11feb54 100644 --- a/src/adcp/types/domains/core/tasks_list_request.py +++ b/src/adcp/types/domains/core/tasks_list_request.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/tasks_list_request.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from pydantic import ConfigDict, Field, StrictBool from ..enums import adcp_protocol, sort_direction, task_status @@ -102,11 +102,14 @@ class Sort(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) - field: Annotated[Field1 | None, Field(description='Field to sort by'), NoExplicitNull] = ( - Field1.created_at - ) + field: Annotated[ + Field1 | None, Field(description='Field to sort by'), NoExplicitNull, OmitUnsetDefault + ] = Field1.created_at direction: Annotated[ - sort_direction.SortDirection | None, Field(description='Sort direction'), NoExplicitNull + sort_direction.SortDirection | None, + Field(description='Sort direction'), + NoExplicitNull, + OmitUnsetDefault, ] = sort_direction.SortDirection.desc @@ -132,6 +135,7 @@ class TasksListRequest(AdcpVersionEnvelope): description='Include full conversation history for each task (may significantly increase response size)' ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/core/transformer.py b/src/adcp/types/domains/core/transformer.py index c107faebd..1c13cfb17 100644 --- a/src/adcp/types/domains/core/transformer.py +++ b/src/adcp/types/domains/core/transformer.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/transformer.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool, model_validator @@ -94,6 +94,7 @@ class Multiplicity(AdCPBaseModel): StrictBool | None, Field(description='Whether this transformer accepts max_creatives.'), NoExplicitNull, + OmitUnsetDefault, ] = False max_creatives_limit: Annotated[ SchemaInt | None, @@ -104,6 +105,7 @@ class Multiplicity(AdCPBaseModel): StrictBool | None, Field(description='Whether this transformer accepts max_variants > 1 / variant_axis.'), NoExplicitNull, + OmitUnsetDefault, ] = False max_variants_limit: Annotated[ SchemaInt | None, @@ -196,6 +198,7 @@ class InputFormat1(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -203,6 +206,7 @@ class InputFormat1(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -309,6 +313,7 @@ class InputFormat2(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -316,6 +321,7 @@ class InputFormat2(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -422,6 +428,7 @@ class InputFormat3(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -429,6 +436,7 @@ class InputFormat3(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -535,6 +543,7 @@ class InputFormat4(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -542,6 +551,7 @@ class InputFormat4(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -648,6 +658,7 @@ class InputFormat5(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -655,6 +666,7 @@ class InputFormat5(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -761,6 +773,7 @@ class InputFormat6(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -768,6 +781,7 @@ class InputFormat6(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -874,6 +888,7 @@ class InputFormat7(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -881,6 +896,7 @@ class InputFormat7(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -987,6 +1003,7 @@ class InputFormat8(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -994,6 +1011,7 @@ class InputFormat8(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1100,6 +1118,7 @@ class InputFormat9(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1107,6 +1126,7 @@ class InputFormat9(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1213,6 +1233,7 @@ class InputFormat10(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1220,6 +1241,7 @@ class InputFormat10(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1326,6 +1348,7 @@ class InputFormat11(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1333,6 +1356,7 @@ class InputFormat11(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1439,6 +1463,7 @@ class InputFormat12(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1446,6 +1471,7 @@ class InputFormat12(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1552,6 +1578,7 @@ class InputFormat13(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1559,6 +1586,7 @@ class InputFormat13(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1665,6 +1693,7 @@ class InputFormat14(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1672,6 +1701,7 @@ class InputFormat14(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1778,6 +1808,7 @@ class InputFormat15(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1785,6 +1816,7 @@ class InputFormat15(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, @@ -1891,6 +1923,7 @@ class InputFormat16(AdCPBaseModel): description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), NoExplicitNull, + OmitUnsetDefault, ] = False experimental: Annotated[ StrictBool | None, @@ -1898,6 +1931,7 @@ class InputFormat16(AdCPBaseModel): description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." ), NoExplicitNull, + OmitUnsetDefault, ] = False format_shape: Annotated[ str | None, diff --git a/src/adcp/types/domains/core/transformer_param.py b/src/adcp/types/domains/core/transformer_param.py index 51341f697..36a6ba9e0 100644 --- a/src/adcp/types/domains/core/transformer_param.py +++ b/src/adcp/types/domains/core/transformer_param.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/transformer_param.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictBool, StrictFloat @@ -115,6 +115,7 @@ class TransformerParam(AdCPBaseModel): description='Whether the buyer MUST supply this field in `config`. When false and no `default` is declared, the agent chooses.' ), NoExplicitNull, + OmitUnsetDefault, ] = False description: Annotated[ str | None, diff --git a/src/adcp/types/domains/core/vast_tracker_constraints.py b/src/adcp/types/domains/core/vast_tracker_constraints.py index 21205d470..61e3a76b3 100644 --- a/src/adcp/types/domains/core/vast_tracker_constraints.py +++ b/src/adcp/types/domains/core/vast_tracker_constraints.py @@ -1,15 +1,15 @@ # generated by datamodel-codegen: # filename: core/vast_tracker_constraints.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull from adcp.types._scalar import ScalarStr -from pydantic import Field, RootModel +from pydantic import Field from ..enums import vast_version @@ -55,7 +55,7 @@ class VastOffset(ScalarStr): _constraints = {'pattern': '^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$'} -class VastVersions(RootModel[list[vast_version.VastVersion]]): +class VastVersions(AdCPRootModel[list[vast_version.VastVersion]]): root: Annotated[list[vast_version.VastVersion], Field(min_length=1)] diff --git a/src/adcp/types/domains/core/wholesale_feed_event.py b/src/adcp/types/domains/core/wholesale_feed_event.py index 1b86c496c..1ef8947e1 100644 --- a/src/adcp/types/domains/core/wholesale_feed_event.py +++ b/src/adcp/types/domains/core/wholesale_feed_event.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/wholesale_feed_event.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from collections.abc import Sequence from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireDatetime, WireUuid +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireDatetime, WireUuid from pydantic import ConfigDict, Field, StrictFloat from ..enums import signal_catalog_type, signal_value_type @@ -157,6 +157,7 @@ class WholesaleFeedEvent3(AdCPBaseModel): Field( description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'collection' for collection_card.* events, 'feed' for wholesale_feed.bulk_change and collection_catalog.bulk_change." ), + OmitUnsetDefault, ] = 'product' entity_id: Annotated[ str, @@ -203,6 +204,7 @@ class WholesaleFeedEvent4(AdCPBaseModel): Field( description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'collection' for collection_card.* events, 'feed' for wholesale_feed.bulk_change and collection_catalog.bulk_change." ), + OmitUnsetDefault, ] = 'product' entity_id: Annotated[ str, @@ -268,6 +270,7 @@ class WholesaleFeedEvent7(AdCPBaseModel): Field( description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'collection' for collection_card.* events, 'feed' for wholesale_feed.bulk_change and collection_catalog.bulk_change." ), + OmitUnsetDefault, ] = 'signal' entity_id: Annotated[ str, @@ -315,6 +318,7 @@ class WholesaleFeedEvent8(AdCPBaseModel): Field( description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'collection' for collection_card.* events, 'feed' for wholesale_feed.bulk_change and collection_catalog.bulk_change." ), + OmitUnsetDefault, ] = 'signal' entity_id: Annotated[ str, @@ -355,6 +359,7 @@ class Payload25(AdCPBaseModel): description='Advisory recommendation. Consumers SHOULD repair by re-reading the affected feed named by affected_entity_type; the recommendation is not a synchronized trigger. Modeled as a single-value enum (not a boolean) so future minor versions can extend the recommendation vocabulary without a breaking shape change. Consumers MUST be prepared to ignore unknown recommendation values.' ), NoExplicitNull, + OmitUnsetDefault, ] = None applies_to: AppliesTo affected_entity_type: Annotated[ @@ -381,6 +386,7 @@ class WholesaleFeedEvent9(AdCPBaseModel): Field( description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'collection' for collection_card.* events, 'feed' for wholesale_feed.bulk_change and collection_catalog.bulk_change." ), + OmitUnsetDefault, ] = 'feed' entity_id: Annotated[ str, @@ -434,6 +440,7 @@ class WholesaleFeedEvent11(AdCPBaseModel): Field( description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'collection' for collection_card.* events, 'feed' for wholesale_feed.bulk_change and collection_catalog.bulk_change." ), + OmitUnsetDefault, ] = 'collection' entity_id: Annotated[ str, @@ -474,6 +481,7 @@ class Payload28(AdCPBaseModel): description='Advisory recommendation, the same vocabulary as wholesale_feed.bulk_change: replace the card mirror from a complete unfiltered list_collections read. Consumers MUST be prepared to ignore unknown recommendation values.' ), NoExplicitNull, + OmitUnsetDefault, ] = None applies_to: AppliesTo @@ -494,6 +502,7 @@ class WholesaleFeedEvent12(AdCPBaseModel): Field( description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'collection' for collection_card.* events, 'feed' for wholesale_feed.bulk_change and collection_catalog.bulk_change." ), + OmitUnsetDefault, ] = 'feed' entity_id: Annotated[ str, @@ -556,6 +565,7 @@ class WholesaleFeedEvent10(AdCPBaseModel): Field( description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'collection' for collection_card.* events, 'feed' for wholesale_feed.bulk_change and collection_catalog.bulk_change." ), + OmitUnsetDefault, ] = 'collection' entity_id: Annotated[ str, @@ -661,6 +671,7 @@ class WholesaleFeedEvent5(AdCPBaseModel): Field( description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'collection' for collection_card.* events, 'feed' for wholesale_feed.bulk_change and collection_catalog.bulk_change." ), + OmitUnsetDefault, ] = 'signal' entity_id: Annotated[ str, @@ -720,6 +731,7 @@ class WholesaleFeedEvent6(AdCPBaseModel): Field( description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'collection' for collection_card.* events, 'feed' for wholesale_feed.bulk_change and collection_catalog.bulk_change." ), + OmitUnsetDefault, ] = 'signal' entity_id: Annotated[ str, @@ -784,6 +796,7 @@ class WholesaleFeedEvent1(AdCPBaseModel): Field( description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'collection' for collection_card.* events, 'feed' for wholesale_feed.bulk_change and collection_catalog.bulk_change." ), + OmitUnsetDefault, ] = 'product' entity_id: Annotated[ str, @@ -846,6 +859,7 @@ class WholesaleFeedEvent2(AdCPBaseModel): Field( description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'collection' for collection_card.* events, 'feed' for wholesale_feed.bulk_change and collection_catalog.bulk_change." ), + OmitUnsetDefault, ] = 'product' entity_id: Annotated[ str, diff --git a/src/adcp/types/domains/creative/list_creative_formats_request.py b/src/adcp/types/domains/creative/list_creative_formats_request.py index b754aceae..e38e556c0 100644 --- a/src/adcp/types/domains/creative/list_creative_formats_request.py +++ b/src/adcp/types/domains/creative/list_creative_formats_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: creative/list_creative_formats_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import account_ref @@ -146,6 +146,7 @@ class ListCreativeFormatsRequestCreativeAgent(AdcpRequest, AdcpVersionEnvelope): description='**DEPRECATED in 3.1. Removed at 4.0.** Use `list_transformers` with `include_pricing` and `account` for transformation and generation pricing. *Legacy 3.x behavior:* include `pricing_options` on each returned format. Requires `account`; when false or omitted, pricing is not computed. Agents supporting the 3.1 format-pricing surface MUST continue to honor this field through 3.x.', ), NoExplicitNull, + OmitUnsetDefault, ] = False account: Annotated[ account_ref.AccountReference | None, diff --git a/src/adcp/types/domains/creative/list_creatives_request.py b/src/adcp/types/domains/creative/list_creatives_request.py index 49c7393dd..c856e58a8 100644 --- a/src/adcp/types/domains/creative/list_creatives_request.py +++ b/src/adcp/types/domains/creative/list_creatives_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: creative/list_creatives_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import account_ref @@ -58,9 +58,13 @@ class Sort(AdCPBaseModel): creative_sort_field.CreativeSortField | None, Field(description='Field to sort by'), NoExplicitNull, + OmitUnsetDefault, ] = creative_sort_field.CreativeSortField.created_date direction: Annotated[ - sort_direction.SortDirection | None, Field(description='Sort direction'), NoExplicitNull + sort_direction.SortDirection | None, + Field(description='Sort direction'), + NoExplicitNull, + OmitUnsetDefault, ] = sort_direction.SortDirection.desc @@ -75,6 +79,7 @@ class ListCreativesRequest(AdcpRequest, AdcpVersionEnvelope): StrictBool | None, Field(description='Include package assignment information in response'), NoExplicitNull, + OmitUnsetDefault, ] = True assignment_projection: Annotated[ AssignmentProjection | None, @@ -82,6 +87,7 @@ class ListCreativesRequest(AdcpRequest, AdcpVersionEnvelope): description='Controls nested assigned_packages projection when assignments are included. all returns up to assignment_limit active assignments per creative. matching returns only assignments matching filters.indicator_types and requires that filter. Use matching for compact indicator discovery; get_media_buys remains the complete authoritative repair path when assignments_truncated is true.' ), NoExplicitNull, + OmitUnsetDefault, ] = AssignmentProjection.all assignment_limit: Annotated[ SchemaInt | None, @@ -91,6 +97,7 @@ class ListCreativesRequest(AdcpRequest, AdcpVersionEnvelope): le=200, ), NoExplicitNull, + OmitUnsetDefault, ] = 50 include_snapshot: Annotated[ StrictBool | None, @@ -98,11 +105,13 @@ class ListCreativesRequest(AdcpRequest, AdcpVersionEnvelope): description='Include a lightweight delivery snapshot per creative (lifetime impressions and last-served date). For detailed performance analytics, use get_creative_delivery.' ), NoExplicitNull, + OmitUnsetDefault, ] = False include_items: Annotated[ StrictBool | None, Field(description='Include items for multi-asset formats like carousels and native ads'), NoExplicitNull, + OmitUnsetDefault, ] = False include_variables: Annotated[ StrictBool | None, @@ -110,6 +119,7 @@ class ListCreativesRequest(AdcpRequest, AdcpVersionEnvelope): description='Include dynamic content variable definitions (DCO slots) for each creative' ), NoExplicitNull, + OmitUnsetDefault, ] = False include_pricing: Annotated[ StrictBool | None, @@ -117,6 +127,7 @@ class ListCreativesRequest(AdcpRequest, AdcpVersionEnvelope): description='Include pricing_options on each creative. Requires account to be provided. When false or omitted, pricing is not computed.' ), NoExplicitNull, + OmitUnsetDefault, ] = False include_purged: Annotated[ StrictBool | None, @@ -124,6 +135,7 @@ class ListCreativesRequest(AdcpRequest, AdcpVersionEnvelope): description="Include soft-purged creative tombstones in the result set. When true, creatives destroyed via `creative.purged` with `purge_kind: soft` surface as tombstone records carrying `purged: true`, `purged_at`, and the purge reason — within the seller's webhook activity retention window (30 days from `purged_at`, MUST match `webhook-activity-record` retention). Hard-purged creatives MUST NOT appear regardless of this flag. When false or omitted, the result set excludes all purged creatives — same default as today." ), NoExplicitNull, + OmitUnsetDefault, ] = False include_webhook_activity: Annotated[ StrictBool | None, @@ -131,6 +143,7 @@ class ListCreativesRequest(AdcpRequest, AdcpVersionEnvelope): description='Include recent webhook activity per creative. When true, each returned creative carries a `webhook_activity[]` array of the most recent fires scoped to that creative — `creative.status_changed` and `creative.purged` deliveries. Adoption of the `webhook_activity[]` pattern per `snapshot-and-log.mdx § Webhook activity log pattern`. Retention is 30 days from `completed_at` (MUST). Three-state presence applies: omitted = seller does not surface; `[]` = persists but no recent fires; non-empty = actual records.' ), NoExplicitNull, + OmitUnsetDefault, ] = False webhook_activity_limit: Annotated[ SchemaInt | None, @@ -140,6 +153,7 @@ class ListCreativesRequest(AdcpRequest, AdcpVersionEnvelope): le=200, ), NoExplicitNull, + OmitUnsetDefault, ] = 50 account: Annotated[ account_ref.AccountReference | None, diff --git a/src/adcp/types/domains/creative/list_creatives_response.py b/src/adcp/types/domains/creative/list_creatives_response.py index fbb2db1a3..3c6dd115a 100644 --- a/src/adcp/types/domains/creative/list_creatives_response.py +++ b/src/adcp/types/domains/creative/list_creatives_response.py @@ -1,17 +1,17 @@ # generated by datamodel-codegen: # filename: creative/list_creatives_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations -from adcp.types.base import AdcpResponse, SchemaInt +from adcp.types.base import AdCPRootModel, AdcpResponse, SchemaInt from adcp.types._str_enum import StrEnum from collections.abc import Sequence from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime -from pydantic import ConfigDict, Field, RootModel, StrictBool, StringConstraints, model_validator +from pydantic import ConfigDict, Field, StrictBool, StringConstraints, model_validator from ..core import account as account_1 from ..core import assignment_tracker @@ -342,7 +342,7 @@ class Assignments(AdCPBaseModel): ] = None -class Assets(RootModel[list[asset_union.AssetVariant]]): +class Assets(AdCPRootModel[list[asset_union.AssetVariant]]): root: Annotated[list[asset_union.AssetVariant], Field(min_length=1)] diff --git a/src/adcp/types/domains/creative/list_transformers_request.py b/src/adcp/types/domains/creative/list_transformers_request.py index 528f98b82..c8b36b0e6 100644 --- a/src/adcp/types/domains/creative/list_transformers_request.py +++ b/src/adcp/types/domains/creative/list_transformers_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: creative/list_transformers_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool @@ -112,6 +112,7 @@ class ListTransformersRequestCreativeAgent(AdcpRequest, AdcpVersionEnvelope): StrictBool | None, Field(description='Include `pricing_options` on each transformer. Requires `account`.'), NoExplicitNull, + OmitUnsetDefault, ] = False account: Annotated[ account_ref.AccountReference | None, diff --git a/src/adcp/types/domains/creative/preview_creative_request.py b/src/adcp/types/domains/creative/preview_creative_request.py index 204b77e1d..09efb19a1 100644 --- a/src/adcp/types/domains/creative/preview_creative_request.py +++ b/src/adcp/types/domains/creative/preview_creative_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: creative/preview_creative_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import context as context_1 @@ -123,6 +123,7 @@ class Request(AdCPBaseModel): preview_output_format.PreviewOutputFormat | None, Field(description='Output format for this preview. Overrides batch-level default.'), NoExplicitNull, + OmitUnsetDefault, ] = preview_output_format.PreviewOutputFormat.url item_limit: Annotated[ SchemaInt | None, @@ -190,6 +191,7 @@ class PreviewCreativeRequest(AdcpRequest, AdcpVersionEnvelope): description="Output format. 'url' returns preview_url (iframe-embeddable URL), 'html' returns preview_html (raw HTML). In batch mode, sets the default for all requests (individual items can override). Default: 'url'." ), NoExplicitNull, + OmitUnsetDefault, ] = preview_output_format.PreviewOutputFormat.url item_limit: Annotated[ SchemaInt | None, @@ -228,6 +230,7 @@ class PreviewCreativeRequest(AdcpRequest, AdcpVersionEnvelope): description="Opt in to an asynchronous preview response. When true, the creative agent MAY return status 'submitted' with a task_id only when rendering has been handed to a queue or external renderer and will continue after the request connection is released. Active processing on an open connection uses working progress instead. The buyer polls get_task_status for completion. When false or absent, the agent MUST return a synchronous preview response or a terminal protocol error; it MUST NOT return the submitted shape. This field applies to preview_creative only; build_creative already defines its own async lifecycle." ), NoExplicitNull, + OmitUnsetDefault, ] = False push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, diff --git a/src/adcp/types/domains/creative/preview_render.py b/src/adcp/types/domains/creative/preview_render.py index 5de96e7e6..d47d6eaf1 100644 --- a/src/adcp/types/domains/creative/preview_render.py +++ b/src/adcp/types/domains/creative/preview_render.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: creative/preview_render.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..core import preview_renderer_metadata @@ -23,6 +23,7 @@ class Embedding(AdCPBaseModel): Field( description='Empty iframe sandbox token set. Provider metadata MUST NOT grant scripts, same-origin, navigation, popups, forms, downloads, or other capabilities.' ), + OmitUnsetDefault, ] = '' requires_https: Annotated[ StrictBool | None, diff --git a/src/adcp/types/domains/creative/sync_creatives_request.py b/src/adcp/types/domains/creative/sync_creatives_request.py index c9b5f3e6e..d312cd543 100644 --- a/src/adcp/types/domains/creative/sync_creatives_request.py +++ b/src/adcp/types/domains/creative/sync_creatives_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: creative/sync_creatives_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool, StrictFloat, model_validator @@ -225,6 +225,7 @@ class SyncCreativesRequest(AdcpRequest, AdcpVersionEnvelope): description='When true, creatives not included in this sync will be archived. Use with caution for full library replacement. A seller that declares creative.stable_creative_ids never archives creatives that entered its library outside sync_creatives; other sellers may archive them, so buyers SHOULD NOT use delete_missing against such sellers when seller-created creatives matter. Invalid when creative_ids is provided — delete_missing applies to the entire library scope, not a filtered subset.' ), NoExplicitNull, + OmitUnsetDefault, ] = False dry_run: Annotated[ StrictBool | None, @@ -232,6 +233,7 @@ class SyncCreativesRequest(AdcpRequest, AdcpVersionEnvelope): description="When true, rehearse this sync_creatives operation without applying it. Validates the actual trafficking request in the seller's current context, including library upsert semantics, creative IDs, assignments, account-scoped gates, and seller policies, then returns what would be created/updated/deleted. This is distinct from validate_input, which only validates manifest structure against canonical/product format targets." ), NoExplicitNull, + OmitUnsetDefault, ] = False validation_mode: Annotated[ validation_mode_1.ValidationMode | None, @@ -239,6 +241,7 @@ class SyncCreativesRequest(AdcpRequest, AdcpVersionEnvelope): description="Validation strictness. 'strict' fails entire sync on any validation error. 'lenient' processes valid creatives and reports errors." ), NoExplicitNull, + OmitUnsetDefault, ] = validation_mode_1.ValidationMode.strict push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, diff --git a/src/adcp/types/domains/enums/frequency_cap_scope.py b/src/adcp/types/domains/enums/frequency_cap_scope.py index ff99c43fe..e3b147d9f 100644 --- a/src/adcp/types/domains/enums/frequency_cap_scope.py +++ b/src/adcp/types/domains/enums/frequency_cap_scope.py @@ -1,15 +1,16 @@ # generated by datamodel-codegen: # filename: enums/frequency_cap_scope.json -# timestamp: 2026-05-02T19:36:29+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from pydantic import Field, RootModel +from adcp.types.base import AdCPRootModel +from pydantic import Field -class FrequencyCapScope(RootModel[Literal['package']]): +class FrequencyCapScope(AdCPRootModel[Literal['package']]): root: Annotated[ Literal['package'], Field(description='Scope for frequency cap application', title='Frequency Cap Scope'), diff --git a/src/adcp/types/domains/enums/media_buy_frequency_cap_control_mode.py b/src/adcp/types/domains/enums/media_buy_frequency_cap_control_mode.py index 3947843bb..088268ea3 100644 --- a/src/adcp/types/domains/enums/media_buy_frequency_cap_control_mode.py +++ b/src/adcp/types/domains/enums/media_buy_frequency_cap_control_mode.py @@ -1,15 +1,16 @@ # generated by datamodel-codegen: # filename: enums/media_buy_frequency_cap_control_mode.json -# timestamp: 2026-09-14T14:16:02+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from pydantic import Field, RootModel +from adcp.types.base import AdCPRootModel +from pydantic import Field -class MediaBuyFrequencyCapControlMode(RootModel[Literal['max_impressions']]): +class MediaBuyFrequencyCapControlMode(AdCPRootModel[Literal['max_impressions']]): root: Annotated[ Literal['max_impressions'], Field( diff --git a/src/adcp/types/domains/enums/universal_macro.py b/src/adcp/types/domains/enums/universal_macro.py index 702b97873..5dbad97ea 100644 --- a/src/adcp/types/domains/enums/universal_macro.py +++ b/src/adcp/types/domains/enums/universal_macro.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: enums/universal_macro.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,8 @@ from adcp.types._str_enum import StrEnum from typing import Any, Annotated -from pydantic import Field, RootModel +from adcp.types.base import AdCPRootModel +from pydantic import Field class UniversalMacro3(StrEnum): @@ -100,7 +101,7 @@ class UniversalMacro4(ScalarStr): } -class UniversalMacro(RootModel[UniversalMacro3 | UniversalMacro4]): +class UniversalMacro(AdCPRootModel[UniversalMacro3 | UniversalMacro4]): root: Annotated[ UniversalMacro3 | UniversalMacro4, Field( diff --git a/src/adcp/types/domains/error_details/requote_required.py b/src/adcp/types/domains/error_details/requote_required.py index ee75c1f39..6b0a1ab30 100644 --- a/src/adcp/types/domains/error_details/requote_required.py +++ b/src/adcp/types/domains/error_details/requote_required.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: error_details/requote_required.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..core import media_buy_change_term_id from ..enums import seller_policy_decline_reason @@ -27,7 +27,7 @@ class EnvelopeField1Item(ScalarStr): _constraints = {'min_length': 1} -class EnvelopeField1(RootModel[list[EnvelopeField1Item]]): +class EnvelopeField1(AdCPRootModel[list[EnvelopeField1Item]]): root: Annotated[ list[EnvelopeField1Item], Field( diff --git a/src/adcp/types/domains/formats/canonical/_base.py b/src/adcp/types/domains/formats/canonical/_base.py index d27654026..ba1632542 100644 --- a/src/adcp/types/domains/formats/canonical/_base.py +++ b/src/adcp/types/domains/formats/canonical/_base.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: formats/canonical/_base.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from adcp.types._scalar import ScalarFloat from pydantic import ConfigDict, Field, StrictBool @@ -81,6 +81,7 @@ class Slot(AdCPBaseModel): StrictBool | None, Field(description='Whether this slot is required for a valid manifest.'), NoExplicitNull, + OmitUnsetDefault, ] = False min: Annotated[ SchemaInt | None, @@ -149,6 +150,7 @@ class Slot(AdCPBaseModel): description="Dispatch hint for `build_creative` and v1↔v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1↔v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`, `audio_vast`), the default `false` is sufficient and the flag MAY be omitted." ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -162,6 +164,7 @@ class CanonicalFormatBase(AdCPBaseModel): description='When true, this canonical or seller narrowing may not work as declared. Adopters SHOULD preflight it with validate_input or in a sandbox and SHOULD NOT route production budget without testing; experimental status never makes the deprecated v1 path preferable. Drivers include unsettled spec shape, an adopter runtime gap, and custom shapes awaiting promotion. This replaces the earlier status plus runtime_status axes. Sellers SHOULD set experimental whenever a canonical or declaration is not production-ready.' ), NoExplicitNull, + OmitUnsetDefault, ] = False deprecated: Annotated[ StrictBool | None, @@ -169,6 +172,7 @@ class CanonicalFormatBase(AdCPBaseModel): description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." ), NoExplicitNull, + OmitUnsetDefault, ] = False v1_translatable: Annotated[ StrictBool | None, @@ -176,6 +180,7 @@ class CanonicalFormatBase(AdCPBaseModel): description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The six inherently-v2 canonicals in 3.2 are `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`, `seller_rendered_stateful_display`, and `coordinated_placements`." ), NoExplicitNull, + OmitUnsetDefault, ] = True since_version: Annotated[ str | None, @@ -220,6 +225,7 @@ class CanonicalFormatBase(AdCPBaseModel): description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." ), NoExplicitNull, + OmitUnsetDefault, ] = False slots: Annotated[ list[Slot] | None, diff --git a/src/adcp/types/domains/formats/canonical/agent_placement.py b/src/adcp/types/domains/formats/canonical/agent_placement.py index 5526c68f7..7fbed07b5 100644 --- a/src/adcp/types/domains/formats/canonical/agent_placement.py +++ b/src/adcp/types/domains/formats/canonical/agent_placement.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: formats/canonical/agent_placement.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import NoExplicitNull, SchemaInt +from adcp.types.base import NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictBool from ._base import CanonicalFormatBase @@ -28,18 +28,21 @@ class CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement(CanonicalFormatBa Field( description="Marked experimental at 3.1 GA: the canonical's tracking model (mention-level impression + attribution, postback shape, cross-surface dedup) is intentionally underspecified for 3.1. Adopters claiming `agent_placement` ship private tracking integrations; buyer agents MUST treat attribution as adapter-defined until the 3.2 tracking-macro spec lands. Promotion to non-experimental gated on the 3.2 tracking-contract spec." ), + OmitUnsetDefault, ] = True v1_translatable: Annotated[ Any | None, Field( description="Inherently new in v2 — AI-surface sponsored mentions weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." ), + OmitUnsetDefault, ] = False slots: Annotated[ Any | None, Field( description="agent_placement has minimal buyer-shipped slots — the surface composes the rendered output from brand context (resolved via the manifest's top-level `brand` BrandRef) plus optional offering_ref and landing_page_url assets. None of these assets are rendered verbatim by the buyer; the agent chooses how to use them." ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'offering_ref', 'asset_type': 'text', 'required': False}, {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, diff --git a/src/adcp/types/domains/formats/canonical/audio_daast.py b/src/adcp/types/domains/formats/canonical/audio_daast.py index ee6adb1b5..7da0b1e20 100644 --- a/src/adcp/types/domains/formats/canonical/audio_daast.py +++ b/src/adcp/types/domains/formats/canonical/audio_daast.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: formats/canonical/audio_daast.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Any -from adcp.types.base import NoExplicitNull, SchemaInt +from adcp.types.base import NoExplicitNull, OmitUnsetDefault, SchemaInt from adcp.types._scalar import ScalarInt from pydantic import ConfigDict, Field, StrictBool @@ -29,6 +29,7 @@ class CanonicalFormatDaastAudio(CanonicalFormatBase): Field( description="Default slots for audio_daast canonical. Buyer ships a DAAST tag (URL or inline XML, 1.0 or 1.1) plus an optional clickthrough URL. Tracking events are inherent to DAAST and don't require explicit slots." ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'daast_tag', 'asset_type': 'daast', 'required': True}, {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, diff --git a/src/adcp/types/domains/formats/canonical/audio_hosted.py b/src/adcp/types/domains/formats/canonical/audio_hosted.py index 29739932a..f2e1aa085 100644 --- a/src/adcp/types/domains/formats/canonical/audio_hosted.py +++ b/src/adcp/types/domains/formats/canonical/audio_hosted.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: formats/canonical/audio_hosted.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import NoExplicitNull, SchemaInt +from adcp.types.base import NoExplicitNull, OmitUnsetDefault, SchemaInt from adcp.types._scalar import ScalarInt from pydantic import ConfigDict, Field, StrictBool, StrictFloat @@ -60,6 +60,7 @@ class CanonicalFormatHostedAudio(CanonicalFormatBase): Field( description="Default slots for buyer-uploaded audio. Host-read products override with a `script` (asset_type: text) or `creative_brief` (asset_type: brief) slot in place of `audio_main`, plus `asset_source: 'publisher_host_recorded'` and `buyer_asset_acceptance: 'rejected'`. TTS-from-script products override similarly with `asset_source: 'seller_pre_rendered_from_brief'`." ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'audio_main', 'asset_type': 'audio', 'required': True}, {'asset_group_id': 'companion_image', 'asset_type': 'image', 'required': False}, @@ -119,6 +120,7 @@ class CanonicalFormatHostedAudio(CanonicalFormatBase): description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." ), NoExplicitNull, + OmitUnsetDefault, ] = AssetSource.buyer_uploaded buyer_asset_acceptance: Annotated[ BuyerAssetAcceptance | None, @@ -126,6 +128,7 @@ class CanonicalFormatHostedAudio(CanonicalFormatBase): description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." ), NoExplicitNull, + OmitUnsetDefault, ] = BuyerAssetAcceptance.accepted companion_image_required: Annotated[StrictBool | None, NoExplicitNull] = None companion_image_aspect_ratio: Annotated[str | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/formats/canonical/audio_vast.py b/src/adcp/types/domains/formats/canonical/audio_vast.py index ae34e0228..7be3c9d2a 100644 --- a/src/adcp/types/domains/formats/canonical/audio_vast.py +++ b/src/adcp/types/domains/formats/canonical/audio_vast.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: formats/canonical/audio_vast.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Any -from adcp.types.base import NoExplicitNull, SchemaInt +from adcp.types.base import NoExplicitNull, OmitUnsetDefault, SchemaInt from adcp.types._scalar import ScalarInt from pydantic import ConfigDict, Field, StrictBool @@ -29,6 +29,7 @@ class CanonicalFormatVastAudio(CanonicalFormatBase): Field( description='Default slots for the audio_vast canonical. The buyer supplies a VAST tag (URL or inline XML) plus an optional clickthrough URL, which falls back to the VAST ClickThrough when omitted. Tracking events and companion creatives carried by the VAST document do not require separate slots.' ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'vast_tag', 'asset_type': 'vast', 'required': True}, {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, diff --git a/src/adcp/types/domains/formats/canonical/coordinated_placements.py b/src/adcp/types/domains/formats/canonical/coordinated_placements.py index 3277fd336..d4ae47fb9 100644 --- a/src/adcp/types/domains/formats/canonical/coordinated_placements.py +++ b/src/adcp/types/domains/formats/canonical/coordinated_placements.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: formats/canonical/coordinated_placements.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from adcp.types._scalar import ScalarFloat, ScalarInt, ScalarStr from pydantic import ConfigDict, Field, StrictBool, StrictFloat @@ -416,6 +416,7 @@ class State(AdCPBaseModel): description='Whether the seller-rendered layout animates within this state (attract loops, load animations). Intra-state animation is seller-rendered; buyer canvases stay static images.' ), NoExplicitNull, + OmitUnsetDefault, ] = Motion.static max_animation_s: Annotated[ StrictFloat | None, @@ -523,6 +524,7 @@ class Transitions(AdCPBaseModel): description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' ), NoExplicitNull, + OmitUnsetDefault, ] = Direction.down transition_mode: Annotated[ TransitionMode, @@ -582,6 +584,7 @@ class Transitions(AdCPBaseModel): description='Whether `video_main` continues without restart while the seller changes state.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -624,6 +627,7 @@ class Transitions1(AdCPBaseModel): description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' ), NoExplicitNull, + OmitUnsetDefault, ] = Direction.down transition_mode: Annotated[ TransitionMode, @@ -683,6 +687,7 @@ class Transitions1(AdCPBaseModel): description='Whether `video_main` continues without restart while the seller changes state.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -724,6 +729,7 @@ class Transitions2(AdCPBaseModel): description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' ), NoExplicitNull, + OmitUnsetDefault, ] = Direction.down transition_mode: Annotated[ TransitionMode, @@ -782,6 +788,7 @@ class Transitions2(AdCPBaseModel): description='Whether `video_main` continues without restart while the seller changes state.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -829,12 +836,14 @@ class Transitions3(AdCPBaseModel): description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' ), NoExplicitNull, + OmitUnsetDefault, ] = Direction.down transition_mode: Annotated[ Literal['scroll_linked'], Field( description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' ), + OmitUnsetDefault, ] = 'scroll_linked' delay_ms: Annotated[ SchemaInt | None, @@ -886,6 +895,7 @@ class Transitions3(AdCPBaseModel): description='Whether `video_main` continues without restart while the seller changes state.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -927,6 +937,7 @@ class Transitions4(AdCPBaseModel): description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' ), NoExplicitNull, + OmitUnsetDefault, ] = Direction.down transition_mode: Annotated[ TransitionMode, @@ -987,6 +998,7 @@ class Transitions4(AdCPBaseModel): description='Whether `video_main` continues without restart while the seller changes state.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -1028,6 +1040,7 @@ class Transitions5(AdCPBaseModel): description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' ), NoExplicitNull, + OmitUnsetDefault, ] = Direction.down transition_mode: Annotated[ TransitionMode, @@ -1088,6 +1101,7 @@ class Transitions5(AdCPBaseModel): description='Whether `video_main` continues without restart while the seller changes state.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -1148,7 +1162,7 @@ class SharedSlot(AdCPBaseModel): ) asset_group_id: Annotated[str, Field(pattern='^[a-z0-9_]+$')] asset_type: AssetType - required: Annotated[StrictBool | None, NoExplicitNull] = False + required: Annotated[StrictBool | None, NoExplicitNull, OmitUnsetDefault] = False min: Annotated[SchemaInt | None, Field(ge=0), NoExplicitNull] = None max: Annotated[SchemaInt | None, Field(ge=1), NoExplicitNull] = None consumed_by: Annotated[ @@ -1220,6 +1234,7 @@ class Params(CanonicalFormatBase): Field( description="Default slots for image canonical. Buyer ships an image asset (file or hosted URL) plus optional headline, body text, primary text (long-form caption), CTA (typically constrained to an enum via `cta_values`), and clickthrough URL. Products MAY override the default — make `headline` required, narrow `cta` to a value enum, or remove slots the surface doesn't consume." ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'image_main', 'asset_type': 'image', 'required': True}, {'asset_group_id': 'headline', 'asset_type': 'text', 'required': False}, @@ -1326,6 +1341,7 @@ class Params(CanonicalFormatBase): description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." ), NoExplicitNull, + OmitUnsetDefault, ] = AssetSource.buyer_uploaded buyer_asset_acceptance: Annotated[ BuyerAssetAcceptance | None, @@ -1333,6 +1349,7 @@ class Params(CanonicalFormatBase): description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." ), NoExplicitNull, + OmitUnsetDefault, ] = BuyerAssetAcceptance.accepted ctv_ad_experience: Annotated[ ctv_ad_experience_1.CtvAdExperience | None, @@ -1416,6 +1433,7 @@ class Params2(CanonicalFormatBase): Field( description="Default slots for html5 canonical. Buyer ships a zip bundle plus optional backup image (required when `backup_image_required: true`) and clickthrough URL. The zip's entry point is typically `index.html`; click handling uses the `clickTag` (or `clickTAG`) macro substituted by the seller at serve time." ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'html5_bundle', 'asset_type': 'zip', 'required': True}, {'asset_group_id': 'backup_image', 'asset_type': 'image', 'required': False}, @@ -1614,6 +1632,7 @@ class Params3(CanonicalFormatBase): Field( description='Backward-compatible URL-delivery slots. `tag_url` MUST use `url_type: ad_request`. A format option accepting `inline_markup` or `paired_redirect` overrides this list with a required slot whose `asset_type` is `display_tag`; the display-tag asset keeps paired redirect URLs atomic.' ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'tag_url', 'asset_type': 'url', 'required': True}, {'asset_group_id': 'backup_image', 'asset_type': 'image', 'required': False}, @@ -1786,12 +1805,14 @@ class Params4(CanonicalFormatBase): Field( description="Inherently new in v2 — multi-card carousels (Meta carousel, Pinterest pin collections, Snap collection ads) weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." ), + OmitUnsetDefault, ] = False slots: Annotated[ Any | None, Field( description="Default slots for image_carousel. The `cards` slot's value in the manifest is an array of [card-asset](/schemas/core/assets/card-asset.json) objects; `min` / `max` constrain card count." ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'cards', 'asset_type': 'card', 'required': True, 'min': 2, 'max': 10}, {'asset_group_id': 'primary_text', 'asset_type': 'text', 'required': False}, @@ -1923,6 +1944,7 @@ class Params5(CanonicalFormatBase): Field( description='Default slots for video_hosted canonical. Buyer ships a video asset (file or hosted URL); optional headline, primary text (long-form caption), CTA (typically constrained via `cta_values`), brand_name (typical for vertical short-form), companion_banner (typical for horizontal instream), and clickthrough URL. Products MAY override or extend the default — e.g., remove `companion_banner` for short-form vertical, narrow `cta` to a value enum, mark `landing_page_url` as required.' ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'video_main', 'asset_type': 'video', 'required': True}, {'asset_group_id': 'headline', 'asset_type': 'text', 'required': False}, @@ -2016,6 +2038,7 @@ class Params5(CanonicalFormatBase): description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' ), NoExplicitNull, + OmitUnsetDefault, ] = AssetSource.buyer_uploaded buyer_asset_acceptance: Annotated[ BuyerAssetAcceptance | None, @@ -2023,6 +2046,7 @@ class Params5(CanonicalFormatBase): description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' ), NoExplicitNull, + OmitUnsetDefault, ] = BuyerAssetAcceptance.accepted ctv_ad_experience: Annotated[ ctv_ad_experience_1.CtvAdExperience | None, @@ -2099,6 +2123,7 @@ class Params6(CanonicalFormatBase): Field( description="Default slots for video_vast canonical. Buyer ships a VAST tag (URL or inline XML, VAST 2.x-4.x) plus an optional clickthrough URL (which falls back to the VAST `ClickThrough` element when omitted). Tracking events are inherent to VAST and don't require explicit slots." ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'vast_tag', 'asset_type': 'vast', 'required': True}, {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, @@ -2309,6 +2334,7 @@ class Params7(CanonicalFormatBase): Field( description="Default slots for buyer-uploaded audio. Host-read products override with a `script` (asset_type: text) or `creative_brief` (asset_type: brief) slot in place of `audio_main`, plus `asset_source: 'publisher_host_recorded'` and `buyer_asset_acceptance: 'rejected'`. TTS-from-script products override similarly with `asset_source: 'seller_pre_rendered_from_brief'`." ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'audio_main', 'asset_type': 'audio', 'required': True}, {'asset_group_id': 'companion_image', 'asset_type': 'image', 'required': False}, @@ -2368,6 +2394,7 @@ class Params7(CanonicalFormatBase): description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." ), NoExplicitNull, + OmitUnsetDefault, ] = AssetSource.buyer_uploaded buyer_asset_acceptance: Annotated[ BuyerAssetAcceptance | None, @@ -2375,6 +2402,7 @@ class Params7(CanonicalFormatBase): description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." ), NoExplicitNull, + OmitUnsetDefault, ] = BuyerAssetAcceptance.accepted companion_image_required: Annotated[StrictBool | None, NoExplicitNull] = None companion_image_aspect_ratio: Annotated[str | None, NoExplicitNull] = None @@ -2450,14 +2478,18 @@ class Params9(CanonicalFormatBase): Field( description='Marked experimental at 3.1 GA: the canonical covers 4 meaningfully different retail-media adapter contracts (Amazon SP, Criteo SP / CitrusAd SP, Pinterest Collection, generative-per-SKU). Adopter contracts vary; buyers MUST validate per-adapter behavior before routing budget. Promotion to non-experimental gated on the #4592 adapter-contract docs work.' ), + OmitUnsetDefault, ] = True v1_translatable: Annotated[ Any | None, Field( description="Inherently new in v2 — retail-media catalog placements weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural, not a registry-coverage gap." ), + OmitUnsetDefault, ] = False - slots: Any | None = [ + slots: Annotated[ + Any | None, OmitUnsetDefault + ] = [ {'asset_group_id': 'source_catalog', 'required': True, 'asset_type': 'catalog'}, {'asset_group_id': 'hero_asset', 'required': False, 'asset_type': 'image'}, {'asset_group_id': 'landing_page_url', 'required': False, 'asset_type': 'url'}, @@ -2509,6 +2541,7 @@ class Params9(CanonicalFormatBase): description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' ), NoExplicitNull, + OmitUnsetDefault, ] = ItemProductionModel.buyer_uploaded ctv_ad_experience: Annotated[ ctv_ad_experience_1.CtvAdExperience | None, @@ -2585,18 +2618,21 @@ class Params10(CanonicalFormatBase): Field( description='Stable at 3.1 GA. Shape mirrors IAB OpenRTB Native 1.2 — the renderer contract is well-established across in-feed native and content-recommendation adopters.' ), + OmitUnsetDefault, ] = False v1_translatable: Annotated[ Any | None, Field( description='Translates to v1 named native formats (e.g., `native_standard`, `native_content`) via the projection registry. Sellers with existing v1 named native formats SHOULD point `v1_format_ref[]` at them.' ), + OmitUnsetDefault, ] = True slots: Annotated[ Any | None, Field( description="Default slot shape for native_in_feed. Mirrors IAB OpenRTB Native 1.2 asset types, including the Native video asset: `video` carries a VAST document (the Native 1.2 `vasttag` field) for video-bearing native units such as CTV menu heroes with focus-triggered playback. Products MAY override (`slots_override` on the projection ref) to narrow per-slot limits (`max_chars` on title/body) or remove unused slots (a content-recommendation slot that doesn't display an icon)." ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'title', 'asset_type': 'text', 'required': True}, {'asset_group_id': 'body_text', 'asset_type': 'text', 'required': False}, @@ -2713,6 +2749,7 @@ class Params10(CanonicalFormatBase): description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." ), NoExplicitNull, + OmitUnsetDefault, ] = AssetSource5.buyer_uploaded buyer_asset_acceptance: Annotated[ BuyerAssetAcceptance | None, @@ -2720,6 +2757,7 @@ class Params10(CanonicalFormatBase): description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' ), NoExplicitNull, + OmitUnsetDefault, ] = BuyerAssetAcceptance.accepted @@ -2789,14 +2827,18 @@ class Params11(CanonicalFormatBase): Field( description="Marked experimental at 3.1 GA: composition is algorithmic (the surface picks combinations and reports per-asset breakdowns), and there's no clean v1-translatable equivalent. Buyers ship asset pools rather than rendered creatives; the surface's per-impression composition cannot be predicted by `validate_input`. Adopters SHOULD validate behavior per surface (Google PMax vs Meta Advantage+ creative differ meaningfully)." ), + OmitUnsetDefault, ] = True v1_translatable: Annotated[ Any | None, Field( description="Inherently new in v2 — algorithmic asset-pool composition (Google PMax / Meta Advantage+ creative) wasn't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." ), + OmitUnsetDefault, ] = False - slots: Any | None = [ + slots: Annotated[ + Any | None, OmitUnsetDefault + ] = [ { 'asset_group_id': 'headlines', 'asset_type': 'text', @@ -2962,18 +3004,21 @@ class Params12(CanonicalFormatBase): Field( description="Marked experimental at 3.1 GA: the canonical's tracking model (mention-level impression + attribution, postback shape, cross-surface dedup) is intentionally underspecified for 3.1. Adopters claiming `agent_placement` ship private tracking integrations; buyer agents MUST treat attribution as adapter-defined until the 3.2 tracking-macro spec lands. Promotion to non-experimental gated on the 3.2 tracking-contract spec." ), + OmitUnsetDefault, ] = True v1_translatable: Annotated[ Any | None, Field( description="Inherently new in v2 — AI-surface sponsored mentions weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." ), + OmitUnsetDefault, ] = False slots: Annotated[ Any | None, Field( description="agent_placement has minimal buyer-shipped slots — the surface composes the rendered output from brand context (resolved via the manifest's top-level `brand` BrandRef) plus optional offering_ref and landing_page_url assets. None of these assets are rendered verbatim by the buyer; the agent chooses how to use them." ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'offering_ref', 'asset_type': 'text', 'required': False}, {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, @@ -3097,27 +3142,31 @@ class Params13(CanonicalFormatBase): Field( description='Experimental in AdCP 3.2 while the creative working group gathers implementation evidence across premium web and mobile/app sellers.' ), + OmitUnsetDefault, ] = True v1_translatable: Annotated[ Any | None, Field( description='No v1 named-format equivalent can express multiple seller-rendered states and their breakpoint bindings.' ), + OmitUnsetDefault, ] = False - since_version: Any | None = '3.2' - composition_model: Any | None = 'deterministic' + since_version: Annotated[Any | None, OmitUnsetDefault] = '3.2' + composition_model: Annotated[Any | None, OmitUnsetDefault] = 'deterministic' supply_mode: Annotated[ SupplyMode | None, Field( description='Which end of the template contract the buyer feeds. `components`: buyer supplies component slots; seller renders states (no `state_canvases`/`layered_source` assets allowed). `rendered_canvases`: buyer supplies exactly one `state_canvases` image per declared state × breakpoint pair. `layered_source`: buyer ships design source (+ optional `font_files`); seller production derives states (`production_window_business_days` applies) — transitional for sellers without executable templates.' ), NoExplicitNull, + OmitUnsetDefault, ] = SupplyMode.components slots: Annotated[ Any | None, Field( description='Default manifest slots; which are consumed depends on `supply_mode`. `state_canvases` images MUST carry `state_id` and `breakpoint_id`, and `state_click_urls` entries MUST carry `state_id` (semantic validators resolve the bindings). Component images SHOULD carry `focal_point` for deterministic seller cropping. `landing_page_url` is the default destination (see `clickthrough`). `font_files` MUST contain only buyer-licensed fonts; publisher-proprietary fonts never travel in manifests. Only image, video, text, url, zip, and pixel_tracker slot asset types are accepted — executable types (javascript, html, css, webhook) are rejected even via `slots` overrides.' ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'state_canvases', 'asset_type': 'image', 'required': False, 'min': 1}, {'asset_group_id': 'logo', 'asset_type': 'image', 'required': False}, @@ -3152,6 +3201,7 @@ class Params13(CanonicalFormatBase): description='How the unit enters view, distinct from state changes. `clip_window`: canvas fixed and progressively exposed through a scrolling window (interscroller, topscroll). `scroll_parallax`: canvas moves at a different rate than content. Reveal is presentation of one canvas, not a transition; do not fabricate a second state to express it.' ), NoExplicitNull, + OmitUnsetDefault, ] = Reveal.none transitions: Annotated[ list[Transitions | Transitions1 | Transitions2 | Transitions3 | Transitions4 | Transitions5] @@ -3168,6 +3218,7 @@ class Params13(CanonicalFormatBase): description='Destination policy. `required` (default): manifest MUST supply `landing_page_url`. `optional`: click-optional units (in-feed brand units) may omit it. `none`: unit is non-clickable; manifests MUST NOT supply `landing_page_url` or `state_click_urls`. Per-state overrides via `state_click_urls` entries carrying `state_id`; `landing_page_url` is the fallback for unlisted states.' ), NoExplicitNull, + OmitUnsetDefault, ] = Clickthrough.required user_controls: Annotated[ UserControls, @@ -3286,6 +3337,7 @@ class Params8(CanonicalFormatBase): Field( description="Default slots for audio_daast canonical. Buyer ships a DAAST tag (URL or inline XML, 1.0 or 1.1) plus an optional clickthrough URL. Tracking events are inherent to DAAST and don't require explicit slots." ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'daast_tag', 'asset_type': 'daast', 'required': True}, {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, @@ -3394,15 +3446,17 @@ class CanonicalFormatCoordinatedPlacements(CanonicalFormatBase): Field( description='Experimental in AdCP 3.2 while the creative working group gathers implementation evidence for atomic cross-placement composition.' ), + OmitUnsetDefault, ] = True v1_translatable: Annotated[ Any | None, Field( description='No v1 named-format equivalent can express a coordinated multi-placement buy.' ), + OmitUnsetDefault, ] = False - since_version: Any | None = '3.2' - composition_model: Any | None = 'deterministic' + since_version: Annotated[Any | None, OmitUnsetDefault] = '3.2' + composition_model: Annotated[Any | None, OmitUnsetDefault] = 'deterministic' components: Annotated[ list[ Components diff --git a/src/adcp/types/domains/formats/canonical/display_tag.py b/src/adcp/types/domains/formats/canonical/display_tag.py index a4afc1125..864a4af99 100644 --- a/src/adcp/types/domains/formats/canonical/display_tag.py +++ b/src/adcp/types/domains/formats/canonical/display_tag.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: formats/canonical/display_tag.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictBool from ._base import CanonicalFormatBase @@ -42,6 +42,7 @@ class CanonicalFormatDisplayTag(CanonicalFormatBase): Field( description='Backward-compatible URL-delivery slots. `tag_url` MUST use `url_type: ad_request`. A format option accepting `inline_markup` or `paired_redirect` overrides this list with a required slot whose `asset_type` is `display_tag`; the display-tag asset keeps paired redirect URLs atomic.' ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'tag_url', 'asset_type': 'url', 'required': True}, {'asset_group_id': 'backup_image', 'asset_type': 'image', 'required': False}, diff --git a/src/adcp/types/domains/formats/canonical/html5.py b/src/adcp/types/domains/formats/canonical/html5.py index bd0ad16fb..4489a8eaf 100644 --- a/src/adcp/types/domains/formats/canonical/html5.py +++ b/src/adcp/types/domains/formats/canonical/html5.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: formats/canonical/html5.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictBool from ._base import CanonicalFormatBase @@ -40,6 +40,7 @@ class CanonicalFormatHtml5Banner(CanonicalFormatBase): Field( description="Default slots for html5 canonical. Buyer ships a zip bundle plus optional backup image (required when `backup_image_required: true`) and clickthrough URL. The zip's entry point is typically `index.html`; click handling uses the `clickTag` (or `clickTAG`) macro substituted by the seller at serve time." ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'html5_bundle', 'asset_type': 'zip', 'required': True}, {'asset_group_id': 'backup_image', 'asset_type': 'image', 'required': False}, diff --git a/src/adcp/types/domains/formats/canonical/image.py b/src/adcp/types/domains/formats/canonical/image.py index 5ac4f29c5..a27ea33c3 100644 --- a/src/adcp/types/domains/formats/canonical/image.py +++ b/src/adcp/types/domains/formats/canonical/image.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: formats/canonical/image.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from adcp.types._scalar import ScalarFloat from pydantic import ConfigDict, Field, StrictBool @@ -67,6 +67,7 @@ class CanonicalFormatImage(CanonicalFormatBase): Field( description="Default slots for image canonical. Buyer ships an image asset (file or hosted URL) plus optional headline, body text, primary text (long-form caption), CTA (typically constrained to an enum via `cta_values`), and clickthrough URL. Products MAY override the default — make `headline` required, narrow `cta` to a value enum, or remove slots the surface doesn't consume." ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'image_main', 'asset_type': 'image', 'required': True}, {'asset_group_id': 'headline', 'asset_type': 'text', 'required': False}, @@ -173,6 +174,7 @@ class CanonicalFormatImage(CanonicalFormatBase): description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." ), NoExplicitNull, + OmitUnsetDefault, ] = AssetSource.buyer_uploaded buyer_asset_acceptance: Annotated[ BuyerAssetAcceptance | None, @@ -180,6 +182,7 @@ class CanonicalFormatImage(CanonicalFormatBase): description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." ), NoExplicitNull, + OmitUnsetDefault, ] = BuyerAssetAcceptance.accepted ctv_ad_experience: Annotated[ ctv_ad_experience_1.CtvAdExperience | None, diff --git a/src/adcp/types/domains/formats/canonical/image_carousel.py b/src/adcp/types/domains/formats/canonical/image_carousel.py index 3c9c90d93..edf9a05ca 100644 --- a/src/adcp/types/domains/formats/canonical/image_carousel.py +++ b/src/adcp/types/domains/formats/canonical/image_carousel.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: formats/canonical/image_carousel.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import NoExplicitNull, SchemaInt +from adcp.types.base import NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictBool from ._base import CanonicalFormatBase @@ -27,12 +27,14 @@ class CanonicalFormatImageCarousel(CanonicalFormatBase): Field( description="Inherently new in v2 — multi-card carousels (Meta carousel, Pinterest pin collections, Snap collection ads) weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." ), + OmitUnsetDefault, ] = False slots: Annotated[ Any | None, Field( description="Default slots for image_carousel. The `cards` slot's value in the manifest is an array of [card-asset](/schemas/core/assets/card-asset.json) objects; `min` / `max` constrain card count." ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'cards', 'asset_type': 'card', 'required': True, 'min': 2, 'max': 10}, {'asset_group_id': 'primary_text', 'asset_type': 'text', 'required': False}, diff --git a/src/adcp/types/domains/formats/canonical/native_in_feed.py b/src/adcp/types/domains/formats/canonical/native_in_feed.py index dd3f79c61..c4bbcd4b5 100644 --- a/src/adcp/types/domains/formats/canonical/native_in_feed.py +++ b/src/adcp/types/domains/formats/canonical/native_in_feed.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: formats/canonical/native_in_feed.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictBool from ...enums import activation_method @@ -69,18 +69,21 @@ class CanonicalFormatNativeInFeed(CanonicalFormatBase): Field( description='Stable at 3.1 GA. Shape mirrors IAB OpenRTB Native 1.2 — the renderer contract is well-established across in-feed native and content-recommendation adopters.' ), + OmitUnsetDefault, ] = False v1_translatable: Annotated[ Any | None, Field( description='Translates to v1 named native formats (e.g., `native_standard`, `native_content`) via the projection registry. Sellers with existing v1 named native formats SHOULD point `v1_format_ref[]` at them.' ), + OmitUnsetDefault, ] = True slots: Annotated[ Any | None, Field( description="Default slot shape for native_in_feed. Mirrors IAB OpenRTB Native 1.2 asset types, including the Native video asset: `video` carries a VAST document (the Native 1.2 `vasttag` field) for video-bearing native units such as CTV menu heroes with focus-triggered playback. Products MAY override (`slots_override` on the projection ref) to narrow per-slot limits (`max_chars` on title/body) or remove unused slots (a content-recommendation slot that doesn't display an icon)." ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'title', 'asset_type': 'text', 'required': True}, {'asset_group_id': 'body_text', 'asset_type': 'text', 'required': False}, @@ -195,6 +198,7 @@ class CanonicalFormatNativeInFeed(CanonicalFormatBase): description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." ), NoExplicitNull, + OmitUnsetDefault, ] = AssetSource.buyer_uploaded buyer_asset_acceptance: Annotated[ BuyerAssetAcceptance | None, @@ -202,4 +206,5 @@ class CanonicalFormatNativeInFeed(CanonicalFormatBase): description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' ), NoExplicitNull, + OmitUnsetDefault, ] = BuyerAssetAcceptance.accepted diff --git a/src/adcp/types/domains/formats/canonical/responsive_creative.py b/src/adcp/types/domains/formats/canonical/responsive_creative.py index 208bc3186..43c5c80e9 100644 --- a/src/adcp/types/domains/formats/canonical/responsive_creative.py +++ b/src/adcp/types/domains/formats/canonical/responsive_creative.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: formats/canonical/responsive_creative.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Any -from adcp.types.base import NoExplicitNull, SchemaInt +from adcp.types.base import NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictBool from ._base import CanonicalFormatBase @@ -21,14 +21,18 @@ class CanonicalFormatResponsiveCreative(CanonicalFormatBase): Field( description="Marked experimental at 3.1 GA: composition is algorithmic (the surface picks combinations and reports per-asset breakdowns), and there's no clean v1-translatable equivalent. Buyers ship asset pools rather than rendered creatives; the surface's per-impression composition cannot be predicted by `validate_input`. Adopters SHOULD validate behavior per surface (Google PMax vs Meta Advantage+ creative differ meaningfully)." ), + OmitUnsetDefault, ] = True v1_translatable: Annotated[ Any | None, Field( description="Inherently new in v2 — algorithmic asset-pool composition (Google PMax / Meta Advantage+ creative) wasn't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." ), + OmitUnsetDefault, ] = False - slots: Any | None = [ + slots: Annotated[ + Any | None, OmitUnsetDefault + ] = [ { 'asset_group_id': 'headlines', 'asset_type': 'text', diff --git a/src/adcp/types/domains/formats/canonical/seller_rendered_stateful_display.py b/src/adcp/types/domains/formats/canonical/seller_rendered_stateful_display.py index 128c50722..a386dbdb9 100644 --- a/src/adcp/types/domains/formats/canonical/seller_rendered_stateful_display.py +++ b/src/adcp/types/domains/formats/canonical/seller_rendered_stateful_display.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: formats/canonical/seller_rendered_stateful_display.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from adcp.types._scalar import ScalarInt, ScalarStr from pydantic import ConfigDict, Field, StrictBool, StrictFloat @@ -141,6 +141,7 @@ class State(AdCPBaseModel): description='Whether the seller-rendered layout animates within this state (attract loops, load animations). Intra-state animation is seller-rendered; buyer canvases stay static images.' ), NoExplicitNull, + OmitUnsetDefault, ] = Motion.static max_animation_s: Annotated[ StrictFloat | None, @@ -248,6 +249,7 @@ class Transitions(AdCPBaseModel): description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' ), NoExplicitNull, + OmitUnsetDefault, ] = Direction.down transition_mode: Annotated[ TransitionMode, @@ -307,6 +309,7 @@ class Transitions(AdCPBaseModel): description='Whether `video_main` continues without restart while the seller changes state.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -349,6 +352,7 @@ class Transitions7(AdCPBaseModel): description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' ), NoExplicitNull, + OmitUnsetDefault, ] = Direction.down transition_mode: Annotated[ TransitionMode, @@ -408,6 +412,7 @@ class Transitions7(AdCPBaseModel): description='Whether `video_main` continues without restart while the seller changes state.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -449,6 +454,7 @@ class Transitions8(AdCPBaseModel): description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' ), NoExplicitNull, + OmitUnsetDefault, ] = Direction.down transition_mode: Annotated[ TransitionMode, @@ -507,6 +513,7 @@ class Transitions8(AdCPBaseModel): description='Whether `video_main` continues without restart while the seller changes state.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -554,12 +561,14 @@ class Transitions9(AdCPBaseModel): description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' ), NoExplicitNull, + OmitUnsetDefault, ] = Direction.down transition_mode: Annotated[ Literal['scroll_linked'], Field( description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' ), + OmitUnsetDefault, ] = 'scroll_linked' delay_ms: Annotated[ SchemaInt | None, @@ -611,6 +620,7 @@ class Transitions9(AdCPBaseModel): description='Whether `video_main` continues without restart while the seller changes state.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -652,6 +662,7 @@ class Transitions10(AdCPBaseModel): description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' ), NoExplicitNull, + OmitUnsetDefault, ] = Direction.down transition_mode: Annotated[ TransitionMode, @@ -712,6 +723,7 @@ class Transitions10(AdCPBaseModel): description='Whether `video_main` continues without restart while the seller changes state.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -753,6 +765,7 @@ class Transitions11(AdCPBaseModel): description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' ), NoExplicitNull, + OmitUnsetDefault, ] = Direction.down transition_mode: Annotated[ TransitionMode, @@ -813,6 +826,7 @@ class Transitions11(AdCPBaseModel): description='Whether `video_main` continues without restart while the seller changes state.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -856,27 +870,31 @@ class CanonicalFormatSellerRenderedStatefulDisplay(CanonicalFormatBase): Field( description='Experimental in AdCP 3.2 while the creative working group gathers implementation evidence across premium web and mobile/app sellers.' ), + OmitUnsetDefault, ] = True v1_translatable: Annotated[ Any | None, Field( description='No v1 named-format equivalent can express multiple seller-rendered states and their breakpoint bindings.' ), + OmitUnsetDefault, ] = False - since_version: Any | None = '3.2' - composition_model: Any | None = 'deterministic' + since_version: Annotated[Any | None, OmitUnsetDefault] = '3.2' + composition_model: Annotated[Any | None, OmitUnsetDefault] = 'deterministic' supply_mode: Annotated[ SupplyMode | None, Field( description='Which end of the template contract the buyer feeds. `components`: buyer supplies component slots; seller renders states (no `state_canvases`/`layered_source` assets allowed). `rendered_canvases`: buyer supplies exactly one `state_canvases` image per declared state × breakpoint pair. `layered_source`: buyer ships design source (+ optional `font_files`); seller production derives states (`production_window_business_days` applies) — transitional for sellers without executable templates.' ), NoExplicitNull, + OmitUnsetDefault, ] = SupplyMode.components slots: Annotated[ Any | None, Field( description='Default manifest slots; which are consumed depends on `supply_mode`. `state_canvases` images MUST carry `state_id` and `breakpoint_id`, and `state_click_urls` entries MUST carry `state_id` (semantic validators resolve the bindings). Component images SHOULD carry `focal_point` for deterministic seller cropping. `landing_page_url` is the default destination (see `clickthrough`). `font_files` MUST contain only buyer-licensed fonts; publisher-proprietary fonts never travel in manifests. Only image, video, text, url, zip, and pixel_tracker slot asset types are accepted — executable types (javascript, html, css, webhook) are rejected even via `slots` overrides.' ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'state_canvases', 'asset_type': 'image', 'required': False, 'min': 1}, {'asset_group_id': 'logo', 'asset_type': 'image', 'required': False}, @@ -911,6 +929,7 @@ class CanonicalFormatSellerRenderedStatefulDisplay(CanonicalFormatBase): description='How the unit enters view, distinct from state changes. `clip_window`: canvas fixed and progressively exposed through a scrolling window (interscroller, topscroll). `scroll_parallax`: canvas moves at a different rate than content. Reveal is presentation of one canvas, not a transition; do not fabricate a second state to express it.' ), NoExplicitNull, + OmitUnsetDefault, ] = Reveal.none transitions: Annotated[ list[ @@ -929,6 +948,7 @@ class CanonicalFormatSellerRenderedStatefulDisplay(CanonicalFormatBase): description='Destination policy. `required` (default): manifest MUST supply `landing_page_url`. `optional`: click-optional units (in-feed brand units) may omit it. `none`: unit is non-clickable; manifests MUST NOT supply `landing_page_url` or `state_click_urls`. Per-state overrides via `state_click_urls` entries carrying `state_id`; `landing_page_url` is the fallback for unlisted states.' ), NoExplicitNull, + OmitUnsetDefault, ] = Clickthrough.required user_controls: Annotated[ UserControls, diff --git a/src/adcp/types/domains/formats/canonical/sponsored_placement.py b/src/adcp/types/domains/formats/canonical/sponsored_placement.py index 4640e87dc..1b0adebf4 100644 --- a/src/adcp/types/domains/formats/canonical/sponsored_placement.py +++ b/src/adcp/types/domains/formats/canonical/sponsored_placement.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: formats/canonical/sponsored_placement.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import NoExplicitNull, SchemaInt +from adcp.types.base import NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictBool from ...enums import catalog_type @@ -53,14 +53,18 @@ class CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven(CanonicalFormatB Field( description='Marked experimental at 3.1 GA: the canonical covers 4 meaningfully different retail-media adapter contracts (Amazon SP, Criteo SP / CitrusAd SP, Pinterest Collection, generative-per-SKU). Adopter contracts vary; buyers MUST validate per-adapter behavior before routing budget. Promotion to non-experimental gated on the #4592 adapter-contract docs work.' ), + OmitUnsetDefault, ] = True v1_translatable: Annotated[ Any | None, Field( description="Inherently new in v2 — retail-media catalog placements weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural, not a registry-coverage gap." ), + OmitUnsetDefault, ] = False - slots: Any | None = [ + slots: Annotated[ + Any | None, OmitUnsetDefault + ] = [ {'asset_group_id': 'source_catalog', 'required': True, 'asset_type': 'catalog'}, {'asset_group_id': 'hero_asset', 'required': False, 'asset_type': 'image'}, {'asset_group_id': 'landing_page_url', 'required': False, 'asset_type': 'url'}, @@ -112,6 +116,7 @@ class CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven(CanonicalFormatB description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' ), NoExplicitNull, + OmitUnsetDefault, ] = ItemProductionModel.buyer_uploaded ctv_ad_experience: Annotated[ ctv_ad_experience_1.CtvAdExperience | None, diff --git a/src/adcp/types/domains/formats/canonical/video_hosted.py b/src/adcp/types/domains/formats/canonical/video_hosted.py index 1adfcb92b..d3903c3f0 100644 --- a/src/adcp/types/domains/formats/canonical/video_hosted.py +++ b/src/adcp/types/domains/formats/canonical/video_hosted.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: formats/canonical/video_hosted.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from adcp.types._scalar import ScalarInt from pydantic import ConfigDict, Field, StrictBool, StrictFloat @@ -131,6 +131,7 @@ class CanonicalFormatHostedVideo(CanonicalFormatBase): Field( description='Default slots for video_hosted canonical. Buyer ships a video asset (file or hosted URL); optional headline, primary text (long-form caption), CTA (typically constrained via `cta_values`), brand_name (typical for vertical short-form), companion_banner (typical for horizontal instream), and clickthrough URL. Products MAY override or extend the default — e.g., remove `companion_banner` for short-form vertical, narrow `cta` to a value enum, mark `landing_page_url` as required.' ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'video_main', 'asset_type': 'video', 'required': True}, {'asset_group_id': 'headline', 'asset_type': 'text', 'required': False}, @@ -224,6 +225,7 @@ class CanonicalFormatHostedVideo(CanonicalFormatBase): description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' ), NoExplicitNull, + OmitUnsetDefault, ] = AssetSource.buyer_uploaded buyer_asset_acceptance: Annotated[ BuyerAssetAcceptance | None, @@ -231,6 +233,7 @@ class CanonicalFormatHostedVideo(CanonicalFormatBase): description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' ), NoExplicitNull, + OmitUnsetDefault, ] = BuyerAssetAcceptance.accepted ctv_ad_experience: Annotated[ ctv_ad_experience_1.CtvAdExperience | None, diff --git a/src/adcp/types/domains/formats/canonical/video_vast.py b/src/adcp/types/domains/formats/canonical/video_vast.py index e0de48c63..909a7662d 100644 --- a/src/adcp/types/domains/formats/canonical/video_vast.py +++ b/src/adcp/types/domains/formats/canonical/video_vast.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: formats/canonical/video_vast.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import NoExplicitNull, SchemaInt +from adcp.types.base import NoExplicitNull, OmitUnsetDefault, SchemaInt from adcp.types._scalar import ScalarInt from pydantic import ConfigDict, Field, StrictBool @@ -50,6 +50,7 @@ class CanonicalFormatVastVideo(CanonicalFormatBase): Field( description="Default slots for video_vast canonical. Buyer ships a VAST tag (URL or inline XML, VAST 2.x-4.x) plus an optional clickthrough URL (which falls back to the VAST `ClickThrough` element when omitted). Tracking events are inherent to VAST and don't require explicit slots." ), + OmitUnsetDefault, ] = [ {'asset_group_id': 'vast_tag', 'asset_type': 'vast', 'required': True}, {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, diff --git a/src/adcp/types/domains/governance/accepted_governance_agents.py b/src/adcp/types/domains/governance/accepted_governance_agents.py index adac694cc..25d589c2b 100644 --- a/src/adcp/types/domains/governance/accepted_governance_agents.py +++ b/src/adcp/types/domains/governance/accepted_governance_agents.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: governance/accepted_governance_agents.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Any, Annotated, Literal -from adcp.types.base import AdCPBaseModel, SchemaInt, WireAnyUrl -from pydantic import ConfigDict, Field, RootModel +from adcp.types.base import AdCPBaseModel, AdCPRootModel, SchemaInt, WireAnyUrl +from pydantic import ConfigDict, Field class AnyOf1(AdCPBaseModel): @@ -64,7 +64,7 @@ class AnyOf2(AdCPBaseModel): ] -class AnyOf(RootModel[AnyOf1 | AnyOf2]): +class AnyOf(AdCPRootModel[AnyOf1 | AnyOf2]): root: Annotated[AnyOf1 | AnyOf2, Field(discriminator='kind')] def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" diff --git a/src/adcp/types/domains/governance/check_governance_request.py b/src/adcp/types/domains/governance/check_governance_request.py index a0fbf39e5..7a3ba3042 100644 --- a/src/adcp/types/domains/governance/check_governance_request.py +++ b/src/adcp/types/domains/governance/check_governance_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: governance/check_governance_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl, WireDatetime from pydantic import ConfigDict, Field, StrictFloat, StringConstraints from ..core import brand_ref, business_entity, canonical_proposal @@ -397,6 +397,7 @@ class CheckGovernanceRequest2(AdcpVersionEnvelope): description="The type of financial commitment being checked. Determines which budget allocation (if any) to validate against. Defaults to 'media_buy' when omitted." ), NoExplicitNull, + OmitUnsetDefault, ] = purchase_type_1.PurchaseType.media_buy budget_period_id: Annotated[ str | None, @@ -476,6 +477,7 @@ class CheckGovernanceRequest2(AdcpVersionEnvelope): description="The phase of an execution-shaped governed action. Ignored for intent-shaped tool+payload requests. 'purchase': initial commitment; 'modification': update to an existing commitment; 'delivery': periodic delivery reporting. Defaults to purchase if omitted. planned_delivery_1.media_buy_id is optional for purchase and required for modification/delivery." ), NoExplicitNull, + OmitUnsetDefault, ] = governance_phase.GovernancePhase.purchase planned_delivery: Annotated[ planned_delivery_1.PlannedDelivery | None, @@ -564,6 +566,7 @@ class CheckGovernanceRequest3(CheckGovernanceRequest1, CheckGovernanceRequest2): description="The type of financial commitment being checked. Determines which budget allocation (if any) to validate against. Defaults to 'media_buy' when omitted." ), NoExplicitNull, + OmitUnsetDefault, ] = purchase_type_1.PurchaseType.media_buy budget_period_id: Annotated[ str | None, @@ -643,6 +646,7 @@ class CheckGovernanceRequest3(CheckGovernanceRequest1, CheckGovernanceRequest2): description="The phase of an execution-shaped governed action. Ignored for intent-shaped tool+payload requests. 'purchase': initial commitment; 'modification': update to an existing commitment; 'delivery': periodic delivery reporting. Defaults to purchase if omitted. planned_delivery_1.media_buy_id is optional for purchase and required for modification/delivery." ), NoExplicitNull, + OmitUnsetDefault, ] = governance_phase.GovernancePhase.purchase planned_delivery: Annotated[ planned_delivery_1.PlannedDelivery | None, diff --git a/src/adcp/types/domains/governance/get_plan_audit_logs_request.py b/src/adcp/types/domains/governance/get_plan_audit_logs_request.py index 9602dd4c3..cd9e392cd 100644 --- a/src/adcp/types/domains/governance/get_plan_audit_logs_request.py +++ b/src/adcp/types/domains/governance/get_plan_audit_logs_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: governance/get_plan_audit_logs_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool, model_validator from ..core import context as context_1 @@ -60,6 +60,7 @@ class GetPlanAuditLogsRequest(AdcpRequest, AdcpVersionEnvelope): StrictBool | None, Field(description='Include the full audit trail. Default: false.'), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/governance/get_plan_audit_logs_response.py b/src/adcp/types/domains/governance/get_plan_audit_logs_response.py index f340ae8b6..62438da95 100644 --- a/src/adcp/types/domains/governance/get_plan_audit_logs_response.py +++ b/src/adcp/types/domains/governance/get_plan_audit_logs_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: governance/get_plan_audit_logs_response.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl, WireDatetime from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..core import attestation_evaluation, attestation_reference @@ -447,6 +447,7 @@ class Delivery(AdCPBaseModel): description='Buyer-declared operational closure of this governance reporting period. When true, governance freezes the period after recording this observation. An unresolved mismatch becomes closed_unresolved and no longer blocks later governance. Closure is not final billing or commercial settlement.' ), NoExplicitNull, + OmitUnsetDefault, ] = False impressions: Annotated[SchemaInt | None, Field(ge=0), NoExplicitNull] = None diff --git a/src/adcp/types/domains/governance/policy_category_definition.py b/src/adcp/types/domains/governance/policy_category_definition.py index 5123d5d3a..398e5d0d4 100644 --- a/src/adcp/types/domains/governance/policy_category_definition.py +++ b/src/adcp/types/domains/governance/policy_category_definition.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: governance/policy_category_definition.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..enums import restricted_attribute @@ -91,6 +91,7 @@ class PolicyCategoryDefinition(AdCPBaseModel): description='When true, any plan declaring this category MUST set plan.human_review_required = true. Use for regulatory regimes that mandate human oversight under GDPR Art 22 or EU AI Act Annex III — fair_housing, fair_lending, fair_employment, pharmaceutical_advertising, and similar high-risk categories. Category-level setting applies to all policies and plans referencing it; policies can override on policy-entry.requires_human_review. Effective immediately regardless of individual policy `effective_date` fields.' ), NoExplicitNull, + OmitUnsetDefault, ] = False industries: Annotated[ list[str] | None, diff --git a/src/adcp/types/domains/governance/policy_entry.py b/src/adcp/types/domains/governance/policy_entry.py index f08102075..a86af105a 100644 --- a/src/adcp/types/domains/governance/policy_entry.py +++ b/src/adcp/types/domains/governance/policy_entry.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: governance/policy_entry.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl from pydantic import ConfigDict, Field, StrictBool from ..core import ext as ext_1 @@ -79,6 +79,7 @@ class PolicyEntry(AdCPBaseModel): description="Origin of this policy. 'registry' = published to the shared AdCP policy registry with full regulatory metadata. 'inline' = authored bespoke for a specific standards configuration, plan, or portfolio. Defaults to 'inline'. Governance agents MUST set 'registry' when publishing to the registry. Within AdCP *task* payloads (every `$ref` to this schema in a request or response), the field is always 'inline' — registry entries are served by the policy registry API, not embedded in task traffic. The x-entity annotation on `policy_id` assumes the task-payload invariant; if a future task schema adopts registry-publishing, split the annotation accordingly (see issue #2685)." ), NoExplicitNull, + OmitUnsetDefault, ] = Source.inline version: Annotated[ str | None, @@ -118,6 +119,7 @@ class PolicyEntry(AdCPBaseModel): description='When true, plans subject to this policy MUST set plan.human_review_required = true. Use for policies that mandate human oversight of decisions affecting data subjects — e.g., GDPR Article 22 (solely automated decisions with legal or similarly significant effects) and EU AI Act Annex III high-risk categories (credit, insurance pricing, recruitment, housing allocation). Governance agents MUST escalate any plan action whose resolved policies include requires_human_review: true. Unlike `enforcement`, this flag applies as soon as the policy is resolved — it is NOT gated by `effective_date`. Art 22 GDPR and similar foundational obligations may predate an AI-Act-specific effective date; the human-review requirement fires regardless.' ), NoExplicitNull, + OmitUnsetDefault, ] = False jurisdictions: Annotated[ list[str] | None, diff --git a/src/adcp/types/domains/governance/report_plan_adjustment_response.py b/src/adcp/types/domains/governance/report_plan_adjustment_response.py index 1b5b443e7..e61972a6c 100644 --- a/src/adcp/types/domains/governance/report_plan_adjustment_response.py +++ b/src/adcp/types/domains/governance/report_plan_adjustment_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: governance/report_plan_adjustment_response.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..core import context as context_1 @@ -87,6 +87,7 @@ class ReportPlanAdjustmentResponse(AdcpResponse, AdcpVersionEnvelope): description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/governance/report_plan_outcome_request.py b/src/adcp/types/domains/governance/report_plan_outcome_request.py index 85cee5374..beaebb520 100644 --- a/src/adcp/types/domains/governance/report_plan_outcome_request.py +++ b/src/adcp/types/domains/governance/report_plan_outcome_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: governance/report_plan_outcome_request.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..core import context as context_1 @@ -89,6 +89,7 @@ class Delivery(AdCPBaseModel): description='Buyer-declared operational closure of this governance reporting period. When true, governance freezes the period after recording this observation. An unresolved mismatch becomes closed_unresolved and no longer blocks later governance. Closure is not final billing or commercial settlement. Only the authenticated plan owner may set this to true; the governance agent MUST reject it from any other authenticated reporter.' ), NoExplicitNull, + OmitUnsetDefault, ] = False impressions: Annotated[SchemaInt | None, Field(ge=0), NoExplicitNull] = None @@ -164,6 +165,7 @@ class ReportPlanOutcomeRequest(AdcpRequest, AdcpVersionEnvelope): description="The type of financial commitment this outcome is for. Must equal the original approved intent's purchase_type. Determines which budget allocation (if any) to charge against. Defaults to 'media_buy' when omitted." ), NoExplicitNull, + OmitUnsetDefault, ] = purchase_type_1.PurchaseType.media_buy outcome: Annotated[outcome_type.OutcomeType, Field(description='Outcome type.')] seller_response: Annotated[ diff --git a/src/adcp/types/domains/governance/report_plan_outcome_response.py b/src/adcp/types/domains/governance/report_plan_outcome_response.py index 986fd42de..cac64a95b 100644 --- a/src/adcp/types/domains/governance/report_plan_outcome_response.py +++ b/src/adcp/types/domains/governance/report_plan_outcome_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: governance/report_plan_outcome_response.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, model_validator, StrictBool, StrictFloat from ..core import context as context_1 @@ -126,6 +126,7 @@ def _status_to_outcome_state(cls, data: Any) -> Any: description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/governance/reported_outcome_error.py b/src/adcp/types/domains/governance/reported_outcome_error.py index 4a76d4dba..4abef1138 100644 --- a/src/adcp/types/domains/governance/reported_outcome_error.py +++ b/src/adcp/types/domains/governance/reported_outcome_error.py @@ -1,15 +1,15 @@ # generated by datamodel-codegen: # filename: governance/reported_outcome_error.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Any, Annotated, Dict -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel, StrictBool, StrictFloat, StringConstraints +from pydantic import ConfigDict, Field, StrictBool, StrictFloat, StringConstraints class Recovery(StrEnum): @@ -28,7 +28,7 @@ class BoundedScalar1(ScalarStr): _constraints = {'max_length': 4000} -class BoundedScalar(RootModel[StrictBool | StrictFloat | BoundedScalar1 | None]): +class BoundedScalar(AdCPRootModel[StrictBool | StrictFloat | BoundedScalar1 | None]): root: StrictBool | StrictFloat | BoundedScalar1 | None def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" @@ -38,7 +38,7 @@ def __getattr__(self, name: str) -> Any: -class BoundedValueLevel31(RootModel[list[BoundedScalar | None]]): +class BoundedValueLevel31(AdCPRootModel[list[BoundedScalar | None]]): root: Annotated[list[BoundedScalar | None], Field(max_length=32)] def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" @@ -51,21 +51,21 @@ def __getattr__(self, name: str) -> Any: BoundedValueLevel3 = BoundedScalar | BoundedValueLevel31 | dict[str, BoundedScalar | None] | None -class BoundedValueLevel21(RootModel[list[BoundedValueLevel3]]): +class BoundedValueLevel21(AdCPRootModel[list[BoundedValueLevel3]]): root: Annotated[list[BoundedValueLevel3], Field(max_length=32)] BoundedValueLevel2 = BoundedScalar | BoundedValueLevel21 | dict[str, BoundedValueLevel3] | None -class BoundedValue1(RootModel[list[BoundedValueLevel2]]): +class BoundedValue1(AdCPRootModel[list[BoundedValueLevel2]]): root: Annotated[list[BoundedValueLevel2], Field(max_length=32)] BoundedValue = BoundedScalar | BoundedValue1 | dict[str, BoundedValueLevel2] | None -class BoundedObject(RootModel[dict[Annotated[str, StringConstraints(max_length=128)], BoundedValue]]): +class BoundedObject(AdCPRootModel[dict[Annotated[str, StringConstraints(max_length=128)], BoundedValue]]): root: Annotated[dict[Annotated[str, StringConstraints(max_length=128)], BoundedValue], Field(max_length=32)] diff --git a/src/adcp/types/domains/governance/sync_plans_request.py b/src/adcp/types/domains/governance/sync_plans_request.py index 022d0638a..ce4399236 100644 --- a/src/adcp/types/domains/governance/sync_plans_request.py +++ b/src/adcp/types/domains/governance/sync_plans_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: governance/sync_plans_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl, WireDatetime from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..core import brand_ref @@ -85,6 +85,7 @@ class Budget(AdCPBaseModel): description='Controls which verified adjustments restore reusable plan headroom. gross_commitment allows verified decommitments only; verified_net_cost also allows buyer-accepted refunds and credits. Both modes retain the original commitment in anti-fragmentation history and expose economic net cost separately.' ), NoExplicitNull, + OmitUnsetDefault, ] = AccountingMode.gross_commitment per_seller_max_pct: Annotated[ StrictFloat | None, @@ -135,6 +136,7 @@ class Budget2(AdCPBaseModel): description='Controls which verified adjustments restore reusable plan headroom. gross_commitment allows verified decommitments only; verified_net_cost also allows buyer-accepted refunds and credits. Both modes retain the original commitment in anti-fragmentation history and expose economic net cost separately.' ), NoExplicitNull, + OmitUnsetDefault, ] = AccountingMode.gross_commitment per_seller_max_pct: Annotated[ StrictFloat | None, @@ -383,6 +385,7 @@ class Plan(AdCPBaseModel): description='When true, the governance agent MUST escalate any action on this plan for human review before execution — regardless of budget. Required by regulations that prohibit solely automated decisions affecting data subjects (GDPR Art 22, EU AI Act Annex III): credit, insurance pricing, recruitment, housing allocation. Governance agents MUST set this to true automatically when any resolved policy or policy_category has requires_human_review: true. Distinct from budget.reallocation_threshold, which only governs budget reallocation autonomy — human_review_required covers decisions affecting individuals (targeting, creative, delivery).' ), NoExplicitNull, + OmitUnsetDefault, ] = False custom_policies: Annotated[ list[policy_entry.PolicyEntry] | None, diff --git a/src/adcp/types/domains/governance/sync_plans_response.py b/src/adcp/types/domains/governance/sync_plans_response.py index e462af91a..60df14a22 100644 --- a/src/adcp/types/domains/governance/sync_plans_response.py +++ b/src/adcp/types/domains/governance/sync_plans_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: governance/sync_plans_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import context as context_1 @@ -103,6 +103,7 @@ class SyncPlansResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/manifest.py b/src/adcp/types/domains/manifest.py index 4c1f80764..e8268d1ef 100644 --- a/src/adcp/types/domains/manifest.py +++ b/src/adcp/types/domains/manifest.py @@ -1,15 +1,15 @@ # generated by datamodel-codegen: # filename: manifest.json -# timestamp: 2026-10-04T18:54:18+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Any -from pydantic import RootModel +from adcp.types.base import AdCPRootModel -class Model(RootModel[Any]): +class Model(AdCPRootModel[Any]): root: Any diff --git a/src/adcp/types/domains/media_buy/accept_proposal_response.py b/src/adcp/types/domains/media_buy/accept_proposal_response.py index 4f1155e2e..a0e2683b4 100644 --- a/src/adcp/types/domains/media_buy/accept_proposal_response.py +++ b/src/adcp/types/domains/media_buy/accept_proposal_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/accept_proposal_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from pydantic import ConfigDict, Field from ..core import canonical_media_buy_action @@ -56,7 +56,7 @@ class AcceptProposalResponse2(AdCPBaseModel): errors: Annotated[list[error.Error], Field(min_length=1)] context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None class AcceptProposalResponse3(AdCPBaseModel): @@ -76,7 +76,7 @@ class AcceptProposalResponse3(AdCPBaseModel): errors: Annotated[list[error.Error] | None, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None class AcceptProposalResponse6(AdcpResponse, AcceptProposalResponse2, AcceptProposalResponse4): @@ -150,7 +150,7 @@ class AcceptProposalResponse1(AdCPBaseModel): ] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None class AcceptProposalResponse5(AdcpResponse, AcceptProposalResponse1, AcceptProposalResponse4): diff --git a/src/adcp/types/domains/media_buy/acceptance_policy_profile.py b/src/adcp/types/domains/media_buy/acceptance_policy_profile.py index 51c7f9cb6..b104cf045 100644 --- a/src/adcp/types/domains/media_buy/acceptance_policy_profile.py +++ b/src/adcp/types/domains/media_buy/acceptance_policy_profile.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: media_buy/acceptance_policy_profile.json -# timestamp: 2026-10-21T12:09:21+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StringConstraints @@ -77,6 +77,7 @@ class Scope(AdCPBaseModel): Field( description='Explicitly states that the scope is not limited by delivery jurisdiction.' ), + OmitUnsetDefault, NoExplicitNull, ] = None diff --git a/src/adcp/types/domains/media_buy/build_creative_request.py b/src/adcp/types/domains/media_buy/build_creative_request.py index 55a88dcec..d9e86bb59 100644 --- a/src/adcp/types/domains/media_buy/build_creative_request.py +++ b/src/adcp/types/domains/media_buy/build_creative_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/build_creative_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool, StrictFloat @@ -379,6 +379,7 @@ class BuildCreativeRequest(AdcpRequest, AdcpVersionEnvelope): description="`execute` (default) produces and bills the creative(s). `estimate` is a DRY RUN: the agent produces nothing and bills nothing, and returns a BuildCreativeEstimate with a projected cost band (cost_low/cost_high) computed against THIS request's actual inputs (script length, brief, catalog size, max_creatives × max_variants) — the band the buyer cannot derive itself, since per_unit gives the rate but not the unit count. Requires the agent to advertise `creative.supports_spend_controls`; otherwise rejected with `UNSUPPORTED_FEATURE`." ), NoExplicitNull, + OmitUnsetDefault, ] = Mode.execute max_spend: Annotated[ MaxSpend | None, @@ -410,6 +411,7 @@ class BuildCreativeRequest(AdcpRequest, AdcpVersionEnvelope): ge=1, ), NoExplicitNull, + OmitUnsetDefault, ] = 1 variant_axis: Annotated[ VariantAxis | None, @@ -424,6 +426,7 @@ class BuildCreativeRequest(AdcpRequest, AdcpVersionEnvelope): description='Advisory hint for how the buyer intends to use the variants. `keep_one` (best-of-N) and `keep_some` signal the agent to set `recommended`/`rank` on returned variants. Advisory only — it does not change what is returned or billed; every produced variant is returned and charged. Keeping is a client act of trafficking the chosen build_variant_id(s).' ), NoExplicitNull, + OmitUnsetDefault, ] = KeepMode.keep_all selection_strategy: Annotated[ creative_selection_strategy.CreativeSelectionStrategy | None, @@ -496,6 +499,7 @@ class BuildCreativeRequest(AdcpRequest, AdcpVersionEnvelope): description="Output format for preview renders when include_preview is true. 'url' returns preview_url (iframe-embeddable URL), 'html' returns preview_html (raw HTML). Ignored when include_preview is false or omitted." ), NoExplicitNull, + OmitUnsetDefault, ] = preview_output_format_1.PreviewOutputFormat.url macro_values: Annotated[ dict[str, str] | None, diff --git a/src/adcp/types/domains/media_buy/buy_products_request.py b/src/adcp/types/domains/media_buy/buy_products_request.py index 79086953a..4d0042e95 100644 --- a/src/adcp/types/domains/media_buy/buy_products_request.py +++ b/src/adcp/types/domains/media_buy/buy_products_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/buy_products_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..core import ( @@ -148,7 +148,7 @@ class BuyProductsRequest(AdcpRequest, AdcpVersionEnvelope): NoExplicitNull, ] = None bidding: Annotated[bidding_policy.BiddingPolicy | None, NoExplicitNull] = None - paused: Annotated[StrictBool | None, NoExplicitNull] = False + paused: Annotated[StrictBool | None, NoExplicitNull, OmitUnsetDefault] = False purchase_order_ref: Annotated[ str | None, Field(max_length=255, min_length=1), NoExplicitNull ] = None diff --git a/src/adcp/types/domains/media_buy/buy_products_response.py b/src/adcp/types/domains/media_buy/buy_products_response.py index 9f541be61..fae3f61e9 100644 --- a/src/adcp/types/domains/media_buy/buy_products_response.py +++ b/src/adcp/types/domains/media_buy/buy_products_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/buy_products_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from pydantic import ConfigDict, Field from ..core import canonical_media_buy_action @@ -56,7 +56,7 @@ class BuyProductsResponse2(AdCPBaseModel): errors: Annotated[list[error.Error], Field(min_length=1)] context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None class BuyProductsResponse3(AdCPBaseModel): @@ -76,7 +76,7 @@ class BuyProductsResponse3(AdCPBaseModel): errors: Annotated[list[error.Error] | None, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None class BuyProductsResponse6(AdcpResponse, BuyProductsResponse2, BuyProductsResponse4): @@ -150,7 +150,7 @@ class BuyProductsResponse1(AdCPBaseModel): ] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None class BuyProductsResponse5(AdcpResponse, BuyProductsResponse1, BuyProductsResponse4): diff --git a/src/adcp/types/domains/media_buy/control_media_buy_request.py b/src/adcp/types/domains/media_buy/control_media_buy_request.py index 161005b50..53194d2bb 100644 --- a/src/adcp/types/domains/media_buy/control_media_buy_request.py +++ b/src/adcp/types/domains/media_buy/control_media_buy_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/control_media_buy_request.json -# timestamp: 2026-10-21T11:25:44+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool, StrictFloat, model_validator from ..core import bidding_policy, canonical_account_ref, canonical_budget_allocation diff --git a/src/adcp/types/domains/media_buy/control_media_buy_response.py b/src/adcp/types/domains/media_buy/control_media_buy_response.py index 9ea2d455a..5f5f20e7a 100644 --- a/src/adcp/types/domains/media_buy/control_media_buy_response.py +++ b/src/adcp/types/domains/media_buy/control_media_buy_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/control_media_buy_response.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field @@ -40,7 +40,7 @@ class ControlMediaBuyResponse2(AdcpResponse, AdCPBaseModel): errors: Annotated[list[error.Error], Field(min_length=1)] context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None class ControlMediaBuyResponse3(AdcpResponse, AdCPBaseModel): @@ -53,7 +53,7 @@ class ControlMediaBuyResponse3(AdcpResponse, AdCPBaseModel): errors: Annotated[list[error.Error] | None, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None class Warning(Warning_1): @@ -63,6 +63,7 @@ class Warning(Warning_1): description='Closed warning vocabulary for the negotiated AdCP release. A non-blocking condition observed while an operation still succeeded. New values are added through normal protocol releases; buyers validate against the negotiated release.', title='Warning Code', ), + OmitUnsetDefault, ] = 'inventory_shortfall_forecast' @@ -89,7 +90,7 @@ class ControlMediaBuyResponse1(AdcpResponse, AdCPBaseModel): ] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None ControlMediaBuyResponse = Annotated[ diff --git a/src/adcp/types/domains/media_buy/create_media_buy_request.py b/src/adcp/types/domains/media_buy/create_media_buy_request.py index bfb541c7c..ad2ba47a6 100644 --- a/src/adcp/types/domains/media_buy/create_media_buy_request.py +++ b/src/adcp/types/domains/media_buy/create_media_buy_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/create_media_buy_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -10,7 +10,7 @@ from collections.abc import Sequence from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl, WireDatetime from pydantic import ConfigDict, Field, StrictBool, StrictFloat, model_validator from ..core import account_ref, bidding_policy, brand_ref @@ -111,6 +111,7 @@ class Opportunity(OpportunityContext): Field( description='Whether the planning cycle remains open. On calls after initial creation, omission means no status update and MUST NOT reopen or close the opportunity implicitly, except that successful create_media_buy proposal execution explicitly infers accepted closure.' ), + OmitUnsetDefault, ] = 'closed' @@ -350,6 +351,7 @@ class CreateMediaBuyRequest(AdcpRequest, AdcpVersionEnvelope): description="Create the media buy in a paused delivery state. When true, and the buy would otherwise be active because creatives are assigned and the flight has started, the seller returns media_buy_status 'paused'. Setup blockers still take precedence: a buy with no creatives remains 'pending_creatives', and a future-dated buy remains 'pending_start' until its flight can start. Defaults to false." ), NoExplicitNull, + OmitUnsetDefault, ] = False push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, diff --git a/src/adcp/types/domains/media_buy/decline_proposals_response.py b/src/adcp/types/domains/media_buy/decline_proposals_response.py index 2839de23f..e359d0785 100644 --- a/src/adcp/types/domains/media_buy/decline_proposals_response.py +++ b/src/adcp/types/domains/media_buy/decline_proposals_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/decline_proposals_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field from ..core import context as context_1 @@ -85,13 +85,13 @@ class DeclineProposalsResponse1(AdcpResponse, AdCPBaseModel): min_length=0, ), ] - status: Literal['submitted'] = 'submitted' + status: Annotated[Literal['submitted'], OmitUnsetDefault] = 'submitted' task_id: Annotated[str | None, Field(min_length=1), NoExplicitNull] = None message: Annotated[str | None, Field(max_length=2000), NoExplicitNull] = None errors: Annotated[list[error.Error] | None, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None class DeclineProposalsResponse2(AdcpResponse, CompactTaskSubmitted): @@ -106,13 +106,13 @@ class DeclineProposalsResponse2(AdcpResponse, CompactTaskSubmitted): ), NoExplicitNull, ] = None - status: Literal['submitted'] = 'submitted' + status: Annotated[Literal['submitted'], OmitUnsetDefault] = 'submitted' task_id: Annotated[str | None, Field(min_length=1), NoExplicitNull] = None message: Annotated[str | None, Field(max_length=2000), NoExplicitNull] = None errors: Annotated[list[error.Error] | None, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None DeclineProposalsResponse = Annotated[ diff --git a/src/adcp/types/domains/media_buy/get_media_buy_delivery_request.py b/src/adcp/types/domains/media_buy/get_media_buy_delivery_request.py index d9b971e9d..590582a23 100644 --- a/src/adcp/types/domains/media_buy/get_media_buy_delivery_request.py +++ b/src/adcp/types/domains/media_buy/get_media_buy_delivery_request.py @@ -1,18 +1,18 @@ # generated by datamodel-codegen: # filename: media_buy/get_media_buy_delivery_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations -from adcp.types.base import AdcpRequest, SchemaInt +from adcp.types.base import AdCPRootModel, AdcpRequest, SchemaInt from typing import Any from pydantic import SerializerFunctionWrapHandler, model_serializer, model_validator from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull -from pydantic import ConfigDict, Field, RootModel, StrictBool +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault +from pydantic import ConfigDict, Field, StrictBool from ..core import account_ref from ..core import context as context_1 @@ -70,7 +70,7 @@ class Spot(AdCPBaseModel): ] = None -class StatusFilter(RootModel[list[media_buy_status.MediaBuyStatus]]): +class StatusFilter(AdCPRootModel[list[media_buy_status.MediaBuyStatus]]): root: Annotated[ list[media_buy_status.MediaBuyStatus], Field( @@ -98,6 +98,7 @@ class CatalogItem(AdCPBaseModel): description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_metric.SortMetric.spend sort_direction: Annotated[ sort_direction_1.SortDirection | None, @@ -105,6 +106,7 @@ class CatalogItem(AdCPBaseModel): description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_direction_1.SortDirection.desc @@ -126,6 +128,7 @@ class Creative(AdCPBaseModel): description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_metric.SortMetric.spend sort_direction: Annotated[ sort_direction_1.SortDirection | None, @@ -133,6 +136,7 @@ class Creative(AdCPBaseModel): description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_direction_1.SortDirection.desc @@ -154,6 +158,7 @@ class Keyword(AdCPBaseModel): description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_metric.SortMetric.spend sort_direction: Annotated[ sort_direction_1.SortDirection | None, @@ -161,6 +166,7 @@ class Keyword(AdCPBaseModel): description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_direction_1.SortDirection.desc @@ -197,6 +203,7 @@ class Geo(AdCPBaseModel): ge=1, ), NoExplicitNull, + OmitUnsetDefault, ] = 25 sort_by: Annotated[ sort_metric.SortMetric | None, @@ -204,6 +211,7 @@ class Geo(AdCPBaseModel): description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_metric.SortMetric.spend sort_direction: Annotated[ sort_direction_1.SortDirection | None, @@ -211,6 +219,7 @@ class Geo(AdCPBaseModel): description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_direction_1.SortDirection.desc @@ -232,6 +241,7 @@ class DeviceType(AdCPBaseModel): description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_metric.SortMetric.spend sort_direction: Annotated[ sort_direction_1.SortDirection | None, @@ -239,6 +249,7 @@ class DeviceType(AdCPBaseModel): description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_direction_1.SortDirection.desc cursor: Annotated[ str | None, @@ -267,6 +278,7 @@ class DevicePlatform(AdCPBaseModel): description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_metric.SortMetric.spend sort_direction: Annotated[ sort_direction_1.SortDirection | None, @@ -274,6 +286,7 @@ class DevicePlatform(AdCPBaseModel): description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_direction_1.SortDirection.desc cursor: Annotated[ str | None, @@ -302,6 +315,7 @@ class Format(AdCPBaseModel): description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_metric.SortMetric.spend sort_direction: Annotated[ sort_direction_1.SortDirection | None, @@ -309,6 +323,7 @@ class Format(AdCPBaseModel): description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_direction_1.SortDirection.desc @@ -320,6 +335,7 @@ class Audience(AdCPBaseModel): SchemaInt | None, Field(description='Maximum number of entries to return. Defaults to 25.', ge=1), NoExplicitNull, + OmitUnsetDefault, ] = 25 sort_by: Annotated[ sort_metric.SortMetric | None, @@ -327,6 +343,7 @@ class Audience(AdCPBaseModel): description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_metric.SortMetric.spend sort_direction: Annotated[ sort_direction_1.SortDirection | None, @@ -334,6 +351,7 @@ class Audience(AdCPBaseModel): description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_direction_1.SortDirection.desc cursor: Annotated[ str | None, @@ -360,6 +378,7 @@ class Demographic(AdCPBaseModel): SchemaInt | None, Field(description='Maximum number of demographic entries to return. Defaults to 25.', ge=1), NoExplicitNull, + OmitUnsetDefault, ] = 25 sort_by: Annotated[ sort_metric.SortMetric | None, @@ -367,6 +386,7 @@ class Demographic(AdCPBaseModel): description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_metric.SortMetric.spend sort_direction: Annotated[ sort_direction_1.SortDirection | None, @@ -374,6 +394,7 @@ class Demographic(AdCPBaseModel): description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_direction_1.SortDirection.desc @@ -385,6 +406,7 @@ class Placement(AdCPBaseModel): SchemaInt | None, Field(description='Maximum number of entries to return. Defaults to 25.', ge=1), NoExplicitNull, + OmitUnsetDefault, ] = 25 sort_by: Annotated[ sort_metric.SortMetric | None, @@ -392,6 +414,7 @@ class Placement(AdCPBaseModel): description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_metric.SortMetric.spend sort_direction: Annotated[ sort_direction_1.SortDirection | None, @@ -399,6 +422,7 @@ class Placement(AdCPBaseModel): description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." ), NoExplicitNull, + OmitUnsetDefault, ] = sort_direction_1.SortDirection.desc cursor: Annotated[ str | None, @@ -580,6 +604,7 @@ class GetMediaBuyDeliveryRequest(AdcpRequest, AdcpVersionEnvelope): description='When true, include daily_breakdown arrays within each package in by_package. Useful for per-package pacing analysis and line-item monitoring. Omit or set false to reduce response size — package daily data can be large for multi-package buys over long flights.' ), NoExplicitNull, + OmitUnsetDefault, ] = False requested_metrics: Annotated[ list[available_metric.AvailableMetric] | None, @@ -602,6 +627,7 @@ class GetMediaBuyDeliveryRequest(AdcpRequest, AdcpVersionEnvelope): description="When true, the response includes media_buy_deliveries[].windows[] — an array of per-window delivery slices over the date range at the requested time_granularity. Ignored when time_granularity is omitted. Each window's payload mirrors what reporting_webhook would have delivered for the same window, enabling lossless GET-path recovery for buyers who missed webhook fires. Omit or set false to reduce response size when only cumulative aggregates are needed." ), NoExplicitNull, + OmitUnsetDefault, ] = False attribution_window: Annotated[ AttributionWindow | None, diff --git a/src/adcp/types/domains/media_buy/get_media_buys_request.py b/src/adcp/types/domains/media_buy/get_media_buys_request.py index e951e789f..8e801ed2d 100644 --- a/src/adcp/types/domains/media_buy/get_media_buys_request.py +++ b/src/adcp/types/domains/media_buy/get_media_buys_request.py @@ -1,15 +1,15 @@ # generated by datamodel-codegen: # filename: media_buy/get_media_buys_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations -from adcp.types.base import AdcpRequest, SchemaInt +from adcp.types.base import AdCPRootModel, AdcpRequest, SchemaInt from typing import Annotated -from adcp.types.base import NoExplicitNull -from pydantic import ConfigDict, Field, RootModel, StrictBool +from adcp.types.base import NoExplicitNull, OmitUnsetDefault +from pydantic import ConfigDict, Field, StrictBool from ..core import account_ref from ..core import context as context_1 @@ -19,7 +19,7 @@ from ..enums import indicator_type, media_buy_status -class StatusFilter(RootModel[list[media_buy_status.MediaBuyStatus]]): +class StatusFilter(AdCPRootModel[list[media_buy_status.MediaBuyStatus]]): root: Annotated[ list[media_buy_status.MediaBuyStatus], Field( @@ -78,6 +78,7 @@ class GetMediaBuysRequest(AdcpRequest, AdcpVersionEnvelope): description='When true, include a near-real-time delivery snapshot for each package. Snapshots reflect the latest available entity-level stats from the platform (e.g., updated every ~15 minutes on GAM, ~1 hour on batch-only platforms). The staleness_seconds field on each snapshot indicates data freshness. If a snapshot cannot be returned, package.snapshot_unavailable_reason explains why. Defaults to false.' ), NoExplicitNull, + OmitUnsetDefault, ] = False include_history: Annotated[ SchemaInt | None, @@ -87,6 +88,7 @@ class GetMediaBuysRequest(AdcpRequest, AdcpVersionEnvelope): le=1000, ), NoExplicitNull, + OmitUnsetDefault, ] = 0 include_webhook_activity: Annotated[ StrictBool | None, @@ -94,6 +96,7 @@ class GetMediaBuysRequest(AdcpRequest, AdcpVersionEnvelope): description="When true, each returned media buy includes a `webhook_activity` array describing recent delivery-report webhook fires for the calling principal. Used by buyer agents to verify whether a publisher actually fired against the buyer's registered endpoint and what the endpoint returned — closes the operator-ticket loop for webhook debugging. Scoped to the calling principal: a buyer sees only fires targeting its own endpoint, even when multiple principals share visibility into the same media buy. Defaults to false. See `webhook_activity_limit` for the per-buy cap." ), NoExplicitNull, + OmitUnsetDefault, ] = False webhook_activity_limit: Annotated[ SchemaInt | None, @@ -103,6 +106,7 @@ class GetMediaBuysRequest(AdcpRequest, AdcpVersionEnvelope): le=200, ), NoExplicitNull, + OmitUnsetDefault, ] = 50 pagination: Annotated[ pagination_request.PaginationRequest | None, diff --git a/src/adcp/types/domains/media_buy/get_media_buys_response.py b/src/adcp/types/domains/media_buy/get_media_buys_response.py index f9d966b23..5f00f60dc 100644 --- a/src/adcp/types/domains/media_buy/get_media_buys_response.py +++ b/src/adcp/types/domains/media_buy/get_media_buys_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/get_media_buys_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -10,7 +10,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..core import account as account_1 @@ -776,6 +776,7 @@ class MediaBuy(IndicatorBearingResourceState): description='Dependency health of the media buy, orthogonal to `status`. `ok` (default) when no upstream resource that this buy depends on is in an offline state. `impaired` when at least one such resource (audience, creative, catalog_item, event_source, property) is offline and affects delivery for one or more packages — `impairments[]` MUST be non-empty in that case. On terminal-status buys, the seller MAY leave this field in whatever state held at the terminal transition. See lifecycle.mdx § Compliance and the impairment.coherence assertion.' ), NoExplicitNull, + OmitUnsetDefault, ] = media_buy_health.MediaBuyHealth.ok impairments: Annotated[ list[impairment.Impairment] | None, diff --git a/src/adcp/types/domains/media_buy/get_products_request.py b/src/adcp/types/domains/media_buy/get_products_request.py index e3cc0a5dc..a4720b768 100644 --- a/src/adcp/types/domains/media_buy/get_products_request.py +++ b/src/adcp/types/domains/media_buy/get_products_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/get_products_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import account_ref, brand_ref @@ -77,6 +77,7 @@ class Refine2(AdCPBaseModel): description="'include' (default): return this product with updated pricing and data. 'omit': exclude this product from the response. 'more_like_this': find additional products similar to this one (the original is also returned). Optional — when omitted, the seller treats the entry as action: 'include'." ), NoExplicitNull, + OmitUnsetDefault, ] = Action.include ask: Annotated[ str | None, @@ -110,6 +111,7 @@ class Refine3(AdCPBaseModel): description="'include' (default): return this proposal with updated allocations and pricing. 'omit': exclude this proposal from the response. 'finalize': request firm pricing and inventory hold. New callers use refine_proposals with action revise for a draft successor or action finalize for a committed held successor; terminal feedback is available through decline_proposals.\n\nLegacy finalize is exclusive within the parent `refine[]` array: see the array-level description for the finalize-exclusivity rule (mixing finalize with non-finalize entries is rejected) and multi-finalize atomicity contract." ), NoExplicitNull, + OmitUnsetDefault, ] = Action10.include ask: Annotated[ str | None, @@ -333,6 +335,7 @@ class GetProductsRequest(AdcpRequest, AdcpVersionEnvelope): description="Experimental (media_buy.collection_cards). When true, asks the seller to return the response-level collections array: the full object for each collection the returned products' collections selectors reference. Sellers MUST NOT return collections unless this is true, and when it is true they SHOULD include every referenced collection. Part of the wholesale_feed_version scope: without it collection copies are outside the version, and with it the version also changes when any returned collection copy changes. This request accepts unknown fields, so a seller that does not implement the feature ignores the flag; buyers detect omitted collections by comparing product selectors with the returned array and resolve those from the publisher's adagents.json." ), NoExplicitNull, + OmitUnsetDefault, ] = False time_budget: Annotated[ duration.Duration | None, diff --git a/src/adcp/types/domains/media_buy/get_products_response.py b/src/adcp/types/domains/media_buy/get_products_response.py index 485bb2436..84e2467db 100644 --- a/src/adcp/types/domains/media_buy/get_products_response.py +++ b/src/adcp/types/domains/media_buy/get_products_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/get_products_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool, StringConstraints diff --git a/src/adcp/types/domains/media_buy/list_collections_response.py b/src/adcp/types/domains/media_buy/list_collections_response.py index 494e73265..666cc6c32 100644 --- a/src/adcp/types/domains/media_buy/list_collections_response.py +++ b/src/adcp/types/domains/media_buy/list_collections_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/list_collections_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field from ..core import context as context_1 @@ -57,7 +57,7 @@ class ListCollectionsResponse1(AdcpResponse, AdCPBaseModel): description='public when the visible set does not depend on the account, and always when the request carried no account; account when it does. A seller MAY return public to a conditional request whose earlier response was account, meaning the account no longer has a distinct visible set; the buyer drops its account-scoped copy and uses the public one.' ), ] - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None @@ -93,7 +93,7 @@ class ListCollectionsResponse2(AdcpResponse, AdCPBaseModel): description='public when the visible set does not depend on the account, and always when the request carried no account; account when it does. A seller MAY return public to a conditional request whose earlier response was account, meaning the account no longer has a distinct visible set; the buyer drops its account-scoped copy and uses the public one.' ), ] - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/media_buy/list_products_request.py b/src/adcp/types/domains/media_buy/list_products_request.py index 752e4190b..16b8d0975 100644 --- a/src/adcp/types/domains/media_buy/list_products_request.py +++ b/src/adcp/types/domains/media_buy/list_products_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/list_products_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import brand_key, canonical_account_ref @@ -88,9 +88,12 @@ class ListProductsRequest(AdcpRequest, AdcpVersionEnvelope): description="Experimental (media_buy.collection_cards). When true, asks the seller to return the response-level collections array: the full object for each collection referenced by the collections selectors of products on the page. Sellers MUST NOT return collections unless this is true, and when it is true they SHOULD include every referenced collection. This request rejects unknown fields, so a seller validating against an earlier schema may reject the flag; send it only to a seller that lists media_buy.collection_cards in experimental_features. Part of the feed_version scope: without it collection copies are outside the version, and with it the version also changes when any returned collection copy changes. Buyers detect omitted collections by comparing product selectors with the returned array and resolve those from the publisher's adagents.json." ), NoExplicitNull, + OmitUnsetDefault, ] = False cursor: Annotated[str | None, Field(min_length=1), NoExplicitNull] = None - max_results: Annotated[SchemaInt | None, Field(ge=1, le=100), NoExplicitNull] = 25 + max_results: Annotated[ + SchemaInt | None, Field(ge=1, le=100), NoExplicitNull, OmitUnsetDefault + ] = 25 if_feed_version: Annotated[ str | None, Field( diff --git a/src/adcp/types/domains/media_buy/list_products_response.py b/src/adcp/types/domains/media_buy/list_products_response.py index a481d8622..385cc86d5 100644 --- a/src/adcp/types/domains/media_buy/list_products_response.py +++ b/src/adcp/types/domains/media_buy/list_products_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/list_products_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field from ..core import canonical_product @@ -105,7 +105,7 @@ class ListProductsResponse1(AdcpResponse, AdCPBaseModel): ), NoExplicitNull, ] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None @@ -166,7 +166,7 @@ class ListProductsResponse2(AdcpResponse, AdCPBaseModel): ), NoExplicitNull, ] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/media_buy/list_proposals_request.py b/src/adcp/types/domains/media_buy/list_proposals_request.py index e37535b74..d6bab5794 100644 --- a/src/adcp/types/domains/media_buy/list_proposals_request.py +++ b/src/adcp/types/domains/media_buy/list_proposals_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/list_proposals_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field @@ -127,5 +127,7 @@ class ListProposalsRequest(AdcpRequest, AdcpVersionEnvelope): NoExplicitNull, ] = None cursor: Annotated[str | None, Field(min_length=1), NoExplicitNull] = None - max_results: Annotated[SchemaInt | None, Field(ge=1, le=100), NoExplicitNull] = 25 + max_results: Annotated[ + SchemaInt | None, Field(ge=1, le=100), NoExplicitNull, OmitUnsetDefault + ] = 25 ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/media_buy/list_proposals_response.py b/src/adcp/types/domains/media_buy/list_proposals_response.py index 0bb523659..1d796af64 100644 --- a/src/adcp/types/domains/media_buy/list_proposals_response.py +++ b/src/adcp/types/domains/media_buy/list_proposals_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/list_proposals_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from pydantic import ConfigDict, Field from ..core import canonical_proposal @@ -92,6 +92,6 @@ class ListProposalsResponse(AdcpResponse, AdCPBaseModel): ), ] next_cursor: Annotated[str | None, Field(min_length=1), NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/media_buy/media_buy_commitment_response.py b/src/adcp/types/domains/media_buy/media_buy_commitment_response.py index 912dd58c6..461137e2a 100644 --- a/src/adcp/types/domains/media_buy/media_buy_commitment_response.py +++ b/src/adcp/types/domains/media_buy/media_buy_commitment_response.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: media_buy/media_buy_commitment_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireDatetime from pydantic import ConfigDict, Field from ..core import canonical_media_buy_action @@ -50,7 +50,7 @@ class MediaBuyCommitmentResponse2(AdCPBaseModel): errors: Annotated[list[error.Error], Field(min_length=1)] context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None class MediaBuyCommitmentResponse3(AdCPBaseModel): @@ -70,7 +70,7 @@ class MediaBuyCommitmentResponse3(AdCPBaseModel): errors: Annotated[list[error.Error] | None, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None class Warning(Warning_1): @@ -136,7 +136,7 @@ class MediaBuyCommitmentResponse1(AdCPBaseModel): ] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None MediaBuyCommitmentResponse = Annotated[ diff --git a/src/adcp/types/domains/media_buy/package_control.py b/src/adcp/types/domains/media_buy/package_control.py index 00d48411b..6dd97e7ae 100644 --- a/src/adcp/types/domains/media_buy/package_control.py +++ b/src/adcp/types/domains/media_buy/package_control.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: media_buy/package_control.json -# timestamp: 2026-10-21T11:25:44+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool, StrictFloat, model_validator diff --git a/src/adcp/types/domains/media_buy/package_request.py b/src/adcp/types/domains/media_buy/package_request.py index 499a547ea..86506a5eb 100644 --- a/src/adcp/types/domains/media_buy/package_request.py +++ b/src/adcp/types/domains/media_buy/package_request.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: media_buy/package_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from collections.abc import Sequence from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from pydantic import ConfigDict, Field, StrictBool, StrictFloat, model_validator from ..core import audience_evidence_pin @@ -227,6 +227,7 @@ class PackageRequest(AdcpVersionEnvelope): description='Whether this package should be created in a paused state. Paused packages do not deliver impressions. Defaults to false.' ), NoExplicitNull, + OmitUnsetDefault, ] = False catalogs: Annotated[ list[catalog.Catalog] | None, diff --git a/src/adcp/types/domains/media_buy/package_update.py b/src/adcp/types/domains/media_buy/package_update.py index 6e9f4908d..7c9f64bb7 100644 --- a/src/adcp/types/domains/media_buy/package_update.py +++ b/src/adcp/types/domains/media_buy/package_update.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: media_buy/package_update.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..core import bidding_policy, catalog diff --git a/src/adcp/types/domains/media_buy/product_discovery_criteria.py b/src/adcp/types/domains/media_buy/product_discovery_criteria.py index 3ee2b9548..7338a0c3a 100644 --- a/src/adcp/types/domains/media_buy/product_discovery_criteria.py +++ b/src/adcp/types/domains/media_buy/product_discovery_criteria.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: media_buy/product_discovery_criteria.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool @@ -49,6 +49,7 @@ class ProductDiscoveryCriteria(AdCPBaseModel): description="Experimental (media_buy.delegated_composition). On request_proposals and refine_proposals, when true, product_ids is the complete composable product universe for the request and its refinement lineage rather than a candidate hint: every proposal purchase, and every product echoed alongside proposals, MUST reference a listed product_id that resolves for the calling account, and every signal the seller selects in a purchase's targeting MUST resolve through a listed product's signal_targeting_options. A listed ID the seller cannot resolve is outside the universe; it is never substituted. A seller that cannot satisfy the brief within the universe returns the rejected outcome (request_proposals) or a partial/unable result with reason constraint_unsatisfiable (refine_proposals); it never returns off-universe products, purchases, or signals. A refinement inherits the source proposal's effective universe: criteria may narrow product_ids (restating product_ids_exclusive true is allowed) but MUST NOT widen them, set the flag on a lineage that did not set it, or set it false — such a refinement is rejected at task level with INVALID_REQUEST naming the field. A product_changes include of an off-universe product returns constraint_unsatisfiable for that entry. Widening requires a new request_proposals. Requires product_ids. Buyers MUST NOT send this flag unless the seller declares media_buy.supports_exclusive_product_ids, and SHOULD verify returned products are a subset of product_ids regardless. Sellers that do not declare the capability MUST NOT silently ignore the flag on request_proposals or refine_proposals — they reject the request at task level with UNSUPPORTED_FEATURE. On list_products the flag is ignorable: product_ids already selects exactly the listed products." ), NoExplicitNull, + OmitUnsetDefault, ] = False offer_filters: Annotated[ product_offer_filters.ProductOfferFilters | None, diff --git a/src/adcp/types/domains/media_buy/product_fields.py b/src/adcp/types/domains/media_buy/product_fields.py index 47983b8b7..f8e5e20df 100644 --- a/src/adcp/types/domains/media_buy/product_fields.py +++ b/src/adcp/types/domains/media_buy/product_fields.py @@ -1,13 +1,14 @@ # generated by datamodel-codegen: # filename: media_buy/product_fields.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from pydantic import Field, RootModel +from adcp.types.base import AdCPRootModel +from pydantic import Field class ProductResponseField(StrEnum): @@ -51,7 +52,7 @@ class ProductResponseField(StrEnum): allowed_actions = 'allowed_actions' -class ProductResponseFields(RootModel[list[ProductResponseField]]): +class ProductResponseFields(AdCPRootModel[list[ProductResponseField]]): root: Annotated[ list[ProductResponseField], Field( diff --git a/src/adcp/types/domains/media_buy/product_refinement.py b/src/adcp/types/domains/media_buy/product_refinement.py index e50a779be..f78d6f8bd 100644 --- a/src/adcp/types/domains/media_buy/product_refinement.py +++ b/src/adcp/types/domains/media_buy/product_refinement.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: media_buy/product_refinement.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull -from pydantic import ConfigDict, Field, RootModel +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull, OmitUnsetDefault +from pydantic import ConfigDict, Field class ProductRefinementRequests2(AdCPBaseModel): @@ -37,7 +37,10 @@ class ProductRefinementRequests3(AdCPBaseModel): Field(description='Product ID from a previous product-discovery response.', min_length=1), ] action: Annotated[ - Action | None, Field(description='Requested product-level action.'), NoExplicitNull + Action | None, + Field(description='Requested product-level action.'), + NoExplicitNull, + OmitUnsetDefault, ] = Action.include ask: Annotated[ str | None, @@ -64,7 +67,10 @@ class ProductRefinementRequests4(AdCPBaseModel): Field(description='Proposal ID from a previous product-discovery response.', min_length=1), ] action: Annotated[ - Action12 | None, Field(description='Requested proposal-level action.'), NoExplicitNull + Action12 | None, + Field(description='Requested proposal-level action.'), + NoExplicitNull, + OmitUnsetDefault, ] = Action12.include ask: Annotated[ str | None, @@ -79,7 +85,7 @@ class ProductRefinementRequests4(AdCPBaseModel): ] -class ProductRefinementRequests(RootModel[list[ProductRefinementRequests1]]): +class ProductRefinementRequests(AdCPRootModel[list[ProductRefinementRequests1]]): root: Annotated[ list[ProductRefinementRequests1], Field( diff --git a/src/adcp/types/domains/media_buy/proposal_refinement.py b/src/adcp/types/domains/media_buy/proposal_refinement.py index 02b491a4f..e500005eb 100644 --- a/src/adcp/types/domains/media_buy/proposal_refinement.py +++ b/src/adcp/types/domains/media_buy/proposal_refinement.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: media_buy/proposal_refinement.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireDatetime from pydantic import ConfigDict, Field, StrictFloat from ..core import product_change_map @@ -145,6 +145,7 @@ class ProposalRefinement2(AdCPBaseModel): description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' ), NoExplicitNull, + OmitUnsetDefault, ] = ChangeKind.amendment constraints: Annotated[ Constraints | None, @@ -187,6 +188,7 @@ class ProposalRefinement2(AdCPBaseModel): Field( description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' ), + OmitUnsetDefault, NoExplicitNull, ] = None @@ -208,6 +210,7 @@ class ProposalRefinement3(AdCPBaseModel): description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' ), NoExplicitNull, + OmitUnsetDefault, ] = ChangeKind.amendment constraints: Annotated[ Constraints1 | None, @@ -250,6 +253,7 @@ class ProposalRefinement3(AdCPBaseModel): Field( description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' ), + OmitUnsetDefault, NoExplicitNull, ] = None @@ -271,6 +275,7 @@ class ProposalRefinement4(AdCPBaseModel): description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' ), NoExplicitNull, + OmitUnsetDefault, ] = ChangeKind.amendment constraints: Annotated[ Constraints2, @@ -312,6 +317,7 @@ class ProposalRefinement4(AdCPBaseModel): Field( description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' ), + OmitUnsetDefault, NoExplicitNull, ] = None @@ -333,6 +339,7 @@ class ProposalRefinement5(AdCPBaseModel): description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' ), NoExplicitNull, + OmitUnsetDefault, ] = ChangeKind.amendment constraints: Annotated[ Constraints3 | None, @@ -374,6 +381,7 @@ class ProposalRefinement5(AdCPBaseModel): Field( description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' ), + OmitUnsetDefault, NoExplicitNull, ] = None @@ -395,6 +403,7 @@ class ProposalRefinement6(AdCPBaseModel): description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' ), NoExplicitNull, + OmitUnsetDefault, ] = ChangeKind.amendment constraints: Annotated[ Constraints4 | None, @@ -436,6 +445,7 @@ class ProposalRefinement6(AdCPBaseModel): Field( description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' ), + OmitUnsetDefault, NoExplicitNull, ] = None @@ -457,6 +467,7 @@ class ProposalRefinement7(AdCPBaseModel): description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' ), NoExplicitNull, + OmitUnsetDefault, ] = ChangeKind.amendment constraints: Annotated[ Constraints5 | None, @@ -498,6 +509,7 @@ class ProposalRefinement7(AdCPBaseModel): Field( description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' ), + OmitUnsetDefault, NoExplicitNull, ] = None @@ -519,6 +531,7 @@ class ProposalRefinement8(AdCPBaseModel): description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' ), NoExplicitNull, + OmitUnsetDefault, ] = ChangeKind.amendment constraints: Annotated[ Constraints6 | None, @@ -560,6 +573,7 @@ class ProposalRefinement8(AdCPBaseModel): Field( description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' ), + OmitUnsetDefault, NoExplicitNull, ] = None @@ -581,6 +595,7 @@ class ProposalRefinement9(AdCPBaseModel): description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' ), NoExplicitNull, + OmitUnsetDefault, ] = ChangeKind.amendment constraints: Annotated[ Constraints7 | None, @@ -623,6 +638,7 @@ class ProposalRefinement9(AdCPBaseModel): Field( description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' ), + OmitUnsetDefault, ] @@ -642,6 +658,7 @@ class ProposalRefinement10(AdCPBaseModel): Field( description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' ), + OmitUnsetDefault, ] = 'cancellation' constraints: Annotated[ Constraints8 | None, @@ -684,6 +701,7 @@ class ProposalRefinement10(AdCPBaseModel): Field( description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' ), + OmitUnsetDefault, NoExplicitNull, ] = None diff --git a/src/adcp/types/domains/media_buy/refine_proposals_request.py b/src/adcp/types/domains/media_buy/refine_proposals_request.py index 4920f3140..464e14fe8 100644 --- a/src/adcp/types/domains/media_buy/refine_proposals_request.py +++ b/src/adcp/types/domains/media_buy/refine_proposals_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/refine_proposals_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import context as context_1 @@ -82,4 +82,5 @@ class RefineProposalsRequest(AdcpRequest, AdcpVersionEnvelope): description="Experimental (media_buy.collection_cards). When true, asks the seller to return the response-level collections array: the full object for each collection the returned products' collections selectors reference. Sellers MUST NOT return collections unless this is true, and when it is true they SHOULD include every referenced collection. This request rejects unknown fields, so a seller validating against an earlier schema may reject the flag; send it only to a seller that lists media_buy.collection_cards in experimental_features. Buyers detect omitted collections by comparing product selectors with the returned array and resolve those from the publisher's adagents.json." ), NoExplicitNull, + OmitUnsetDefault, ] = False diff --git a/src/adcp/types/domains/media_buy/refine_proposals_response.py b/src/adcp/types/domains/media_buy/refine_proposals_response.py index 2eb1ff796..dd0972ffe 100644 --- a/src/adcp/types/domains/media_buy/refine_proposals_response.py +++ b/src/adcp/types/domains/media_buy/refine_proposals_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/refine_proposals_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictFloat @@ -830,7 +830,7 @@ class RefineProposalsResponse1(AdcpResponse, AdCPBaseModel): ), NoExplicitNull, ] = None - status: Literal['completed'] = 'completed' + status: Annotated[Literal['completed'], OmitUnsetDefault] = 'completed' task_id: Annotated[str | None, Field(min_length=1), NoExplicitNull] = None message: Annotated[str | None, Field(max_length=2000), NoExplicitNull] = None expected_confirmation_at: Annotated[ @@ -843,7 +843,7 @@ class RefineProposalsResponse1(AdcpResponse, AdCPBaseModel): errors: Annotated[list[error.Error] | None, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None class RefineProposalsResponse2(AdcpResponse, CompactTaskSubmitted): @@ -895,7 +895,7 @@ class RefineProposalsResponse2(AdcpResponse, CompactTaskSubmitted): errors: Annotated[list[error.Error] | None, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None RefineProposalsResponse = Annotated[ diff --git a/src/adcp/types/domains/media_buy/request_proposals_request.py b/src/adcp/types/domains/media_buy/request_proposals_request.py index 0db9dfbbf..6b69fb7f8 100644 --- a/src/adcp/types/domains/media_buy/request_proposals_request.py +++ b/src/adcp/types/domains/media_buy/request_proposals_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/request_proposals_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool, model_validator from ..core import brand_key, canonical_account_ref @@ -34,6 +34,7 @@ class Opportunity(OpportunityContext): Field( description='Whether the planning cycle remains open. On calls after initial creation, omission means no status update and MUST NOT reopen or close the opportunity implicitly, except that successful create_media_buy proposal execution explicitly infers accepted closure.' ), + OmitUnsetDefault, ] = 'open' @@ -121,6 +122,7 @@ class RequestProposalsRequest(AdcpRequest, AdcpVersionEnvelope): description="Experimental (media_buy.collection_cards). When true, asks the seller to return the response-level collections array: the full object for each collection the returned products' collections selectors reference. Sellers MUST NOT return collections unless this is true, and when it is true they SHOULD include every referenced collection. This request rejects unknown fields, so a seller validating against an earlier schema may reject the flag; send it only to a seller that lists media_buy.collection_cards in experimental_features. Buyers detect omitted collections by comparing product selectors with the returned array and resolve those from the publisher's adagents.json." ), NoExplicitNull, + OmitUnsetDefault, ] = False @model_validator(mode='after') diff --git a/src/adcp/types/domains/media_buy/request_proposals_response.py b/src/adcp/types/domains/media_buy/request_proposals_response.py index 506abb928..d234162bc 100644 --- a/src/adcp/types/domains/media_buy/request_proposals_response.py +++ b/src/adcp/types/domains/media_buy/request_proposals_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/request_proposals_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field @@ -262,13 +262,13 @@ class RequestProposalsResponse1(AdcpResponse, AdCPBaseModel): ), NoExplicitNull, ] = None - status: Literal['completed'] = 'completed' + status: Annotated[Literal['completed'], OmitUnsetDefault] = 'completed' task_id: Annotated[str | None, Field(min_length=1), NoExplicitNull] = None message: Annotated[str | None, Field(max_length=2000), NoExplicitNull] = None errors: Annotated[list[error.Error] | None, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None class RequestProposalsResponse2(AdcpResponse, AdCPBaseModel): @@ -326,13 +326,13 @@ class RequestProposalsResponse2(AdcpResponse, AdCPBaseModel): ), NoExplicitNull, ] = None - status: Literal['completed'] = 'completed' + status: Annotated[Literal['completed'], OmitUnsetDefault] = 'completed' task_id: Annotated[str | None, Field(min_length=1), NoExplicitNull] = None message: Annotated[str | None, Field(max_length=2000), NoExplicitNull] = None errors: Annotated[list[error.Error] | None, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None class RequestProposalsResponse3(AdcpResponse, AdCPBaseModel): @@ -393,13 +393,13 @@ class RequestProposalsResponse3(AdcpResponse, AdCPBaseModel): ), NoExplicitNull, ] = None - status: Literal['completed'] = 'completed' + status: Annotated[Literal['completed'], OmitUnsetDefault] = 'completed' task_id: Annotated[str | None, Field(min_length=1), NoExplicitNull] = None message: Annotated[str | None, Field(max_length=2000), NoExplicitNull] = None errors: Annotated[list[error.Error] | None, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None class RequestProposalsResponse4(AdcpResponse, CompactTaskSubmitted): @@ -466,7 +466,7 @@ class RequestProposalsResponse4(AdcpResponse, CompactTaskSubmitted): errors: Annotated[list[error.Error] | None, NoExplicitNull] = None context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None - replayed: Annotated[Literal[True] | None, NoExplicitNull] = None + replayed: Annotated[Literal[True] | None, OmitUnsetDefault, NoExplicitNull] = None RequestProposalsResponse = Annotated[ diff --git a/src/adcp/types/domains/media_buy/sync_audiences_request.py b/src/adcp/types/domains/media_buy/sync_audiences_request.py index 3db210466..d03a41eca 100644 --- a/src/adcp/types/domains/media_buy/sync_audiences_request.py +++ b/src/adcp/types/domains/media_buy/sync_audiences_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/sync_audiences_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool @@ -134,6 +134,7 @@ class SyncAudiencesRequest(AdcpRequest, AdcpVersionEnvelope): description='When true, buyer-managed audiences on the account not included in this sync will be removed. Does not affect seller-managed audiences. Do not combine with an omitted audiences array or all buyer-managed audiences will be deleted.' ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/media_buy/sync_catalogs_request.py b/src/adcp/types/domains/media_buy/sync_catalogs_request.py index 33a67b60c..c7e5ee5c2 100644 --- a/src/adcp/types/domains/media_buy/sync_catalogs_request.py +++ b/src/adcp/types/domains/media_buy/sync_catalogs_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/sync_catalogs_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import ( @@ -83,6 +83,7 @@ class SyncCatalogsRequest(AdcpRequest, AdcpVersionEnvelope): description='When true, buyer-managed catalogs on the account not included in this sync will be removed. Does not affect seller-managed catalogs. Requires catalogs; item_availability_updates alone cannot define deletion scope.' ), NoExplicitNull, + OmitUnsetDefault, ] = False dry_run: Annotated[ StrictBool | None, @@ -90,6 +91,7 @@ class SyncCatalogsRequest(AdcpRequest, AdcpVersionEnvelope): description='When true, preview catalog create, update, and delete changes without applying them. MUST NOT be combined with item_availability_updates.' ), NoExplicitNull, + OmitUnsetDefault, ] = False validation_mode: Annotated[ validation_mode_1.ValidationMode | None, @@ -97,6 +99,7 @@ class SyncCatalogsRequest(AdcpRequest, AdcpVersionEnvelope): description="Validation strictness for semantically valid-looking catalog and item entries. In strict mode (default), an unknown, inaccessible, unauthorized, or stale-generation catalog/item reference, a known seller-managed catalog, a stale expected_overlay_revision, or another per-entry error fails the entire operation before any catalog or availability mutation. In lenient mode, the seller returns a positionally matched failed result for each such item entry and processes the remaining valid entries. Unknown, inaccessible, unauthorized, and stale-generation references MUST be observationally equivalent: code REFERENCE_NOT_FOUND, message exactly 'Catalog item not found', recovery 'correctable', and no field, suggestion, retry_after, issues, details, or resource metadata. Authorization and lookup MUST use the same externally observable failure path and SHOULD avoid materially distinguishable timing. A known seller-managed catalog may use INVALID_REQUEST only after catalog access is authorized. Request-schema failures, duplicate identity tuples, unsupported capability, batch-limit excess, dry_run conflicts, and mixed requests that cannot commit synchronously and atomically are operation-level failures before lookup or mutation in both modes. A stale revision uses CONFLICT without mutation." ), NoExplicitNull, + OmitUnsetDefault, ] = validation_mode_1.ValidationMode.strict push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, diff --git a/src/adcp/types/domains/media_buy/sync_event_sources_request.py b/src/adcp/types/domains/media_buy/sync_event_sources_request.py index 4928bde11..9205c9123 100644 --- a/src/adcp/types/domains/media_buy/sync_event_sources_request.py +++ b/src/adcp/types/domains/media_buy/sync_event_sources_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/sync_event_sources_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool @@ -106,6 +106,7 @@ class SyncEventSourcesRequest(AdcpRequest, AdcpVersionEnvelope): StrictBool | None, Field(description='When true, event sources not included in this sync will be removed'), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/media_buy/update_media_buy_request.py b/src/adcp/types/domains/media_buy/update_media_buy_request.py index a583135d9..49dc13d8b 100644 --- a/src/adcp/types/domains/media_buy/update_media_buy_request.py +++ b/src/adcp/types/domains/media_buy/update_media_buy_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/update_media_buy_request.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from collections.abc import Sequence from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..core import account_ref, bidding_policy diff --git a/src/adcp/types/domains/pricing_options/cpc_option.py b/src/adcp/types/domains/pricing_options/cpc_option.py index 31ca2ef3b..b88a6491f 100644 --- a/src/adcp/types/domains/pricing_options/cpc_option.py +++ b/src/adcp/types/domains/pricing_options/cpc_option.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: pricing_options/cpc_option.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..enums import adjustment_kind @@ -53,6 +53,7 @@ class CpcPricingOption(AdCPBaseModel): description='DEPRECATED in 3.2 and removed in the next major. Legacy hint used only to normalize package bid_price to bidding.max_bid (true) or bidding.bid_amount (false/absent). New buyers express intent directly in bidding.', ), NoExplicitNull, + OmitUnsetDefault, ] = False price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, diff --git a/src/adcp/types/domains/pricing_options/cpcv_option.py b/src/adcp/types/domains/pricing_options/cpcv_option.py index dc4418cec..e5547cedb 100644 --- a/src/adcp/types/domains/pricing_options/cpcv_option.py +++ b/src/adcp/types/domains/pricing_options/cpcv_option.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: pricing_options/cpcv_option.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..enums import adjustment_kind @@ -55,6 +55,7 @@ class CpcvPricingOption(AdCPBaseModel): description='DEPRECATED in 3.2 and removed in the next major. Legacy hint used only to normalize package bid_price to bidding.max_bid (true) or bidding.bid_amount (false/absent). New buyers express intent directly in bidding.', ), NoExplicitNull, + OmitUnsetDefault, ] = False price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, diff --git a/src/adcp/types/domains/pricing_options/cpm_option.py b/src/adcp/types/domains/pricing_options/cpm_option.py index cf7296f88..5f465f7d4 100644 --- a/src/adcp/types/domains/pricing_options/cpm_option.py +++ b/src/adcp/types/domains/pricing_options/cpm_option.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: pricing_options/cpm_option.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..enums import adjustment_kind @@ -53,6 +53,7 @@ class CpmPricingOption(AdCPBaseModel): description='DEPRECATED in 3.2 and removed in the next major. Legacy hint used only to normalize package bid_price to bidding.max_bid (true) or bidding.bid_amount (false/absent). New buyers express intent directly in bidding.', ), NoExplicitNull, + OmitUnsetDefault, ] = False price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, diff --git a/src/adcp/types/domains/pricing_options/cpv_option.py b/src/adcp/types/domains/pricing_options/cpv_option.py index 5ff4604c6..535c44dd8 100644 --- a/src/adcp/types/domains/pricing_options/cpv_option.py +++ b/src/adcp/types/domains/pricing_options/cpv_option.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: pricing_options/cpv_option.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from adcp.types._scalar import ScalarFloat from pydantic import ConfigDict, Field, StrictBool, StrictFloat @@ -76,6 +76,7 @@ class CpvPricingOption(AdCPBaseModel): description='DEPRECATED in 3.2 and removed in the next major. Legacy hint used only to normalize package bid_price to bidding.max_bid (true) or bidding.bid_amount (false/absent). New buyers express intent directly in bidding.', ), NoExplicitNull, + OmitUnsetDefault, ] = False price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, diff --git a/src/adcp/types/domains/pricing_options/vcpm_option.py b/src/adcp/types/domains/pricing_options/vcpm_option.py index 42c98ce22..6956f4a7c 100644 --- a/src/adcp/types/domains/pricing_options/vcpm_option.py +++ b/src/adcp/types/domains/pricing_options/vcpm_option.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: pricing_options/vcpm_option.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool, StrictFloat from ..enums import adjustment_kind @@ -55,6 +55,7 @@ class VcpmPricingOption(AdCPBaseModel): description='DEPRECATED in 3.2 and removed in the next major. Legacy hint used only to normalize package bid_price to bidding.max_bid (true) or bidding.bid_amount (false/absent). New buyers express intent directly in bidding.', ), NoExplicitNull, + OmitUnsetDefault, ] = False price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, diff --git a/src/adcp/types/domains/property/create_property_list_response.py b/src/adcp/types/domains/property/create_property_list_response.py index 53bb68957..c63bb249e 100644 --- a/src/adcp/types/domains/property/create_property_list_response.py +++ b/src/adcp/types/domains/property/create_property_list_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: property/create_property_list_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import context as context_1 @@ -35,6 +35,7 @@ class CreatePropertyListResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnve description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/property/delete_property_list_response.py b/src/adcp/types/domains/property/delete_property_list_response.py index 456e2b969..14f1e99e5 100644 --- a/src/adcp/types/domains/property/delete_property_list_response.py +++ b/src/adcp/types/domains/property/delete_property_list_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: property/delete_property_list_response.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import context as context_1 @@ -29,6 +29,7 @@ class DeletePropertyListResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnve description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/property/get_property_list_request.py b/src/adcp/types/domains/property/get_property_list_request.py index 01fa88182..0290579dd 100644 --- a/src/adcp/types/domains/property/get_property_list_request.py +++ b/src/adcp/types/domains/property/get_property_list_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: property/get_property_list_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import account_ref @@ -25,6 +25,7 @@ class Pagination(AdCPBaseModel): SchemaInt | None, Field(description='Maximum number of identifiers to return per page', ge=1, le=10000), NoExplicitNull, + OmitUnsetDefault, ] = 1000 cursor: Annotated[ str | None, @@ -51,6 +52,7 @@ class GetPropertyListRequest(AdcpRequest, AdcpVersionEnvelope): description='Whether to apply filters and return resolved identifiers (default: true)' ), NoExplicitNull, + OmitUnsetDefault, ] = True pagination: Annotated[ Pagination | None, diff --git a/src/adcp/types/domains/property/property_list.py b/src/adcp/types/domains/property/property_list.py index 7ef37da83..1df359d9c 100644 --- a/src/adcp/types/domains/property/property_list.py +++ b/src/adcp/types/domains/property/property_list.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: property/property_list.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl, WireDatetime from pydantic import ConfigDict, Field from ..core import account_ref, brand_ref, vendor_pricing_option @@ -60,6 +60,7 @@ class PropertyList(AdCPBaseModel): ge=1, ), NoExplicitNull, + OmitUnsetDefault, ] = 24 created_at: Annotated[ WireDatetime | None, Field(description='When the list was created'), NoExplicitNull diff --git a/src/adcp/types/domains/property/update_property_list_response.py b/src/adcp/types/domains/property/update_property_list_response.py index 0bbeaca36..89b49f145 100644 --- a/src/adcp/types/domains/property/update_property_list_response.py +++ b/src/adcp/types/domains/property/update_property_list_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: property/update_property_list_response.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import context as context_1 @@ -29,6 +29,7 @@ class UpdatePropertyListResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnve description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/property/validate_property_delivery_request.py b/src/adcp/types/domains/property/validate_property_delivery_request.py index 6ddfbe1a6..516ae297b 100644 --- a/src/adcp/types/domains/property/validate_property_delivery_request.py +++ b/src/adcp/types/domains/property/validate_property_delivery_request.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: property/validate_property_delivery_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import account_ref @@ -42,6 +42,7 @@ class ValidatePropertyDeliveryRequest(AdcpVersionEnvelope): description='Include compliant records in results (default: only return non_compliant, unmodeled, and unidentified)' ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/protocol/get_adcp_capabilities_response.py b/src/adcp/types/domains/protocol/get_adcp_capabilities_response.py index e0be92a89..6b51df36e 100644 --- a/src/adcp/types/domains/protocol/get_adcp_capabilities_response.py +++ b/src/adcp/types/domains/protocol/get_adcp_capabilities_response.py @@ -1,16 +1,16 @@ # generated by datamodel-codegen: # filename: protocol/get_adcp_capabilities_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations -from adcp.types.base import AdcpResponse, SchemaInt +from adcp.types.base import AdCPRootModel, AdcpResponse, SchemaInt from datetime import date from adcp.types._str_enum import StrEnum from typing import Any, Annotated, Dict, Literal -from adcp.types.base import AdCPBaseModel, GeoPlaceSystemKey, NoExplicitNull, WireAnyUrl, WireDatetime +from adcp.types.base import AdCPBaseModel, GeoPlaceSystemKey, NoExplicitNull, OmitUnsetDefault, WireAnyUrl, WireDatetime from adcp.types._scalar import ScalarInt, ScalarStr from pydantic import ( ConfigDict, @@ -167,6 +167,7 @@ class Idempotency(AdCPBaseModel): description="When true, the seller derives `account_id` via an HKDF-based one-way transform of the buyer's natural account key rather than echoing the natural key on the wire. Buyers MUST NOT attempt to invert the opaque id and MUST treat it as a blind handle scoped to this seller. Absent or false, callers should assume `account_id` is the natural key (or a server-assigned but non-opaque id). This flag does not change the wire shape, but it DOES change buyer behavior — buyers MUST NOT cache, log, or treat `account_id` as a natural-key analog when this flag is true. Migration note for sellers already returning an opaque id without this flag: set it to true at the next capabilities refresh so buyers stop inferring natural-key semantics; until set, new-buyer replay/retry logic will misclassify these ids as natural keys." ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -373,7 +374,7 @@ class Tasks9(AdCPBaseModel): class Tasks( - RootModel[Tasks1 | Tasks2 | Tasks3 | Tasks4 | Tasks5 | Tasks6 | Tasks7 | Tasks8 | Tasks9] + AdCPRootModel[Tasks1 | Tasks2 | Tasks3 | Tasks4 | Tasks5 | Tasks6 | Tasks7 | Tasks8 | Tasks9] ): root: Annotated[ Tasks1 | Tasks2 | Tasks3 | Tasks4 | Tasks5 | Tasks6 | Tasks7 | Tasks8 | Tasks9, @@ -450,6 +451,7 @@ class Notifications2(AdCPBaseModel): description='Whether `list_accounts` can return recent delivery attempts in `accounts[].webhook_activity[]` when the buyer requests `include_webhook_activity: true`.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -631,6 +633,7 @@ class ProposalRefinement(AdCPBaseModel): description="Whether the seller accepts action extend_hold on refine_proposals (feature id media_buy.hold_extension). This is the agent-level gate for the action: buyers MUST NOT send extend_hold unless this is true, and a seller that does not declare it fails the unknown action with VALIDATION_ERROR. extend_hold applies only to committed proposals that hold inventory; against a committed proposal with no hold (an entry whose effective policy is terms-only: hold_policy.available false, or no hold_policy at a seller declaring hold_semantics terms_only) the seller fails the request at task level with INVALID_STATE. When the committed proposal carries hold_policy, extendable false or absent (it defaults to false) means the seller declines with hold_unavailable and buyers SHOULD NOT send extend_hold; the successor's expires_at MUST NOT exceed the original hold_placed_at plus max_total_duration, which successors carry forward unchanged across the whole extension chain; a request the seller cannot extend at all returns hold_unavailable. Absent hold_policy (a seller that does not list media_buy.hold_policy), limits remain seller policy expressed through hold_unavailable." ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -658,6 +661,7 @@ class OpenOpportunities(AdCPBaseModel): description='Whether the seller implements list_proposals. Required true when later_proposals.supported is true, because list_proposals is the repair read for proposal.created. Sellers declaring media_buy.seller_offers also require it, as the repair read for proposal.offered, the buy-scoped sibling notification for seller offers against accepted media buys. Seller offers are distinct from later proposals: later_proposals covers new_media_buy drafts under buyer consent, and media_buy_update proposals are out of scope for it.' ), NoExplicitNull, + OmitUnsetDefault, ] = False keep_open_after_purchase: Annotated[ StrictBool | None, @@ -665,6 +669,7 @@ class OpenOpportunities(AdCPBaseModel): description='Whether accept_proposal and buy_products accept an explicit opportunity.status open.' ), NoExplicitNull, + OmitUnsetDefault, ] = False later_proposals: Annotated[ LaterProposals | None, @@ -685,6 +690,7 @@ class PerformanceFeedback(AdCPBaseModel): description='Whether provide_performance_feedback responses include an honest application_status of accepted, applied, or not_applied. accepted is explicitly not an application claim. Sellers that cannot determine optimizer disposition omit this claim or set it false.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -749,6 +755,7 @@ class RelationshipNotifications(AdCPBaseModel): description='Whether the declared repair tasks can return recent delivery attempts for these account-level invalidations when include_webhook_activity is true.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -969,6 +976,7 @@ class Portfolio(AdCPBaseModel): description="Whether `publisher_domains` enumerates every publisher this seller represents. Absent or `true` means the list is complete as served. `false` means the seller represents more publishers than it published — because the real set exceeds the advisory item bound or the per-field byte budget — and the list is a sample. This flag is advisory and affects prefetch planning only, never an authorization decision: buyers MUST NOT treat `publisher_domains` as an allowlist whether it is marked complete or not, and MUST resolve each product's publisher through that publisher's own adagents.json. Added in 3.3 so that bounding this counterparty-asserted array cannot silently turn a large but legitimate network into a seller that looks unauthorized for most of its inventory." ), NoExplicitNull, + OmitUnsetDefault, ] = True primary_channels: Annotated[ list[channels.MediaChannel] | None, @@ -1056,6 +1064,7 @@ class Signals(AdCPBaseModel): description="Whether `data_provider_domains` enumerates every data provider this agent resells. Absent or `true` means the list is complete as served. `false` means the agent resells more providers than it published — because the real set exceeds the advisory item bound or the per-field byte budget — and the list is a sample. This flag is advisory and affects prefetch planning only, never an authorization decision: buyers MUST NOT treat `data_provider_domains` as an allowlist whether it is marked complete or not, and MUST resolve each signal's provider through that provider's own adagents.json. Meaningless without `data_provider_domains`, which the sibling `dependencies` rule requires." ), NoExplicitNull, + OmitUnsetDefault, ] = True discovery_modes: Annotated[ list[DiscoveryMode] | None, @@ -1064,6 +1073,7 @@ class Signals(AdCPBaseModel): min_length=1, ), NoExplicitNull, + OmitUnsetDefault, ] = [DiscoveryMode.brief] features: Annotated[ Features | None, Field(description='Optional signals features supported'), NoExplicitNull @@ -1267,6 +1277,7 @@ class Multiplicity(AdCPBaseModel): description='When true, build_creative accepts max_creatives (one distinct creative per catalog item).' ), NoExplicitNull, + OmitUnsetDefault, ] = False max_creatives_limit: Annotated[ SchemaInt | None, @@ -1279,6 +1290,7 @@ class Multiplicity(AdCPBaseModel): description='Experimental (x-status: experimental) — agents setting this true MUST also list `creative.signal_fanout` in `experimental_features`; the surface MAY change between 3.x releases with notice (see docs/reference/experimental-status). When true, build_creative accepts signal_conditions[] (one distinct creative group per signal condition, keep-all). Mirrors supports_catalog_fanout.' ), NoExplicitNull, + OmitUnsetDefault, ] = False max_signal_conditions_limit: Annotated[ SchemaInt | None, @@ -1294,6 +1306,7 @@ class Multiplicity(AdCPBaseModel): description='When true, build_creative accepts max_variants > 1 / variant_axis (alternatives per creative).' ), NoExplicitNull, + OmitUnsetDefault, ] = False max_variants_limit: Annotated[ SchemaInt | None, @@ -1454,6 +1467,7 @@ class RequestSigning(AdCPBaseModel): validate_default=True, ), NoExplicitNull, + OmitUnsetDefault, ] = [] warn_for: Annotated[ list[Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')]] | None, @@ -1462,6 +1476,7 @@ class RequestSigning(AdCPBaseModel): validate_default=True, ), NoExplicitNull, + OmitUnsetDefault, ] = [] supported_for: Annotated[ list[Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')]] | None, @@ -1477,6 +1492,7 @@ class RequestSigning(AdCPBaseModel): validate_default=True, ), NoExplicitNull, + OmitUnsetDefault, ] = [] protocol_methods_warn_for: Annotated[ list[ProtocolMethodsWarnForItem] | None, @@ -1485,6 +1501,7 @@ class RequestSigning(AdCPBaseModel): validate_default=True, ), NoExplicitNull, + OmitUnsetDefault, ] = [] protocol_methods_required_for: Annotated[ list[ProtocolMethodsRequiredForItem] | None, @@ -1493,6 +1510,7 @@ class RequestSigning(AdCPBaseModel): validate_default=True, ), NoExplicitNull, + OmitUnsetDefault, ] = [] operation_sources: Annotated[ list[OperationSource] | None, @@ -1509,6 +1527,7 @@ class RequestSigning(AdCPBaseModel): min_length=1, ), NoExplicitNull, + OmitUnsetDefault, ] = [Profile.adcp_request_signing_v1] @@ -1555,6 +1574,7 @@ class CompromiseNotification(AdCPBaseModel): StrictBool | None, Field(description='Whether this agent emits `identity.compromise_notification` events.'), NoExplicitNull, + OmitUnsetDefault, ] = False accepts: Annotated[ StrictBool | None, @@ -1562,6 +1582,7 @@ class CompromiseNotification(AdCPBaseModel): description='Whether this agent subscribes to `identity.compromise_notification` events from counterparties it verifies signatures from.' ), NoExplicitNull, + OmitUnsetDefault, ] = False @@ -1582,6 +1603,7 @@ class Identity(AdCPBaseModel): description="When true, this multi-principal operator scopes signing keys per-principal so a single principal's key compromise does not silently re-scope across principals served by the same operator. `kid` values remain opaque to verifiers per RFC 7517; any operator-side naming convention (e.g., `{operator}:{principal}:{key_version}`) is internal bookkeeping and MUST NOT be parsed by verifiers. See docs/building/understanding/security-model.mdx." ), NoExplicitNull, + OmitUnsetDefault, ] = False key_origins: Annotated[ KeyOrigins | None, @@ -2189,6 +2211,7 @@ class Brand(AdCPBaseModel): description='Supports get_rights and acquire_rights for rights discovery and clearance' ), NoExplicitNull, + OmitUnsetDefault, ] = False right_types: Annotated[ list[right_type.RightType] | None, @@ -2246,6 +2269,7 @@ class WebhookSigning(AdCPBaseModel): description='Identifier of the webhook-signing profile version the agent emits. Value MUST match the `tag=` parameter emitted in the RFC 9421 `Signature-Input` header (see docs/building/by-layer/L3/webhooks.mdx) so receivers can statically validate the declared profile against the on-wire tag. Closed enum; future profile revisions will extend this enum in a follow-up schema bump.' ), NoExplicitNull, + OmitUnsetDefault, ] = None algorithms: Annotated[ list[Algorithm] | None, @@ -2262,6 +2286,7 @@ class WebhookSigning(AdCPBaseModel): description='Whether this agent will fall back to HMAC-SHA256 on the legacy push_notification_config.authentication, accounts[].notification_configs[].authentication, sync_principal.configuration.notification_configs[].authentication, sync_agent_notification_configs.notification_configs[].authentication, reporting_webhook.authentication, or create_media_buy.artifact_webhook.authentication paths for receivers that have not adopted RFC 9421. Deprecated; removed in AdCP 4.0.', ), NoExplicitNull, + OmitUnsetDefault, ] = False delivery_retry_horizon_seconds: Annotated[ SchemaInt | None, @@ -2279,6 +2304,7 @@ class WebhookSigning(AdCPBaseModel): min_length=1, ), NoExplicitNull, + OmitUnsetDefault, ] = [webhook_signing_profile.WebhookSigningProfile.adcp_webhook_signing_v1] @@ -2346,6 +2372,7 @@ class Measurement(AdCPBaseModel): description="Whether this measurement agent produces compact provide_performance_feedback assertions for a buyer-controlled orchestrator gateway. In the first experimental tier the provider reads delivery through the gateway's get_media_buy_delivery task and returns assertions through its provide_performance_feedback task. The gateway decides what to forward to each seller." ), NoExplicitNull, + OmitUnsetDefault, ] = False metrics: Annotated[ list[Metric], @@ -2363,6 +2390,7 @@ class Account(AdCPBaseModel): description="Whether the seller requires operator-level credentials. This declares who must authenticate; it does not by itself declare whether OAuth is used, whether list_accounts is exposed, or which sync_accounts modes are supported. When true, operators authenticate independently with the seller and account-scoped calls use seller/storefront-assigned account_id values because the seller or upstream platform owns the canonical account namespace. If a credential may access more than one account, the seller MUST expose list_accounts and buyers MUST resolve an explicit account_id before the first account-scoped request. If a credential is bound to exactly one account, the seller SHOULD expose list_accounts returning that singleton; a seller MAY omit list_accounts only when it provides the same explicit account_id through another declared path or out-of-band onboarding. When false (default, buyer-declared accounts), the seller trusts the agent's identity claims and account-scoped calls use the advertiser natural key: brand + operator + optional operator_unit, fixed currency, optional buyer-selected account timezone, and sandbox. operator_unit.id is owned by the operator and is distinct from the seller's account_id. The seller normally provisions through sync_accounts, but MAY lazily provision on the first provisioning task (one that commits spend or creates account-owned resources, such as create_media_buy) when billing and other required settings are unambiguous from capabilities or onboarding defaults. Discovery and negotiation tasks MUST NOT provision; on them, a natural key that is not yet provisioned returns ACCOUNT_NOT_FOUND unless a lazy-provisioning seller answers for the account it would create. See /docs/accounts/overview#account-references-before-provisioning. A lazy-provisioning seller MUST keep accepting the natural key and MUST expose list_accounts for recovery; if buyer input is needed before use, the seller MUST expose sync_accounts." ), NoExplicitNull, + OmitUnsetDefault, ] = False authorization_endpoint: Annotated[ WireAnyUrl | None, @@ -2377,6 +2405,7 @@ class Account(AdCPBaseModel): description="Whether the seller-assigned account_id is a durable AccountRef for every account the caller can operate, including buyer-declared accounts. When true, the seller MUST return account_id on every non-failed sync_accounts provisioning result, MUST accept { account_id } wherever an AccountRef is accepted, and MUST NOT change an account's account_id for the life of the account, including across identity reconciliation. A buyer-declared seller that declares true still accepts the account's current natural key throughout 3.x. Buyers holding an account_id from a seller that declares true SHOULD send it in place of the natural key. When false or absent, buyers follow the account model declared by require_operator_auth and MUST NOT assume a buyer-declared seller accepts its returned account_id. Sellers whose accounts already use account_id references (require_operator_auth: true) SHOULD declare true when their ids meet these rules. Using the natural key as the reference after provisioning is deprecated as of 3.3 and planned for removal in 4.0, so sellers SHOULD declare true during 3.x to give buyers a migration path." ), NoExplicitNull, + OmitUnsetDefault, ] = False supported_billing: Annotated[ list[billing_party.BillingParty], @@ -2406,6 +2435,7 @@ class Account(AdCPBaseModel): description='Whether an account reference is required for get_products. When true, the buyer must establish an account before browsing products. When false (default), the buyer can browse products without an account — useful for price comparison and discovery before committing to a seller. Before an account is provisioned, the buyer omits account rather than sending a natural key that does not resolve yet. See /docs/accounts/overview#account-references-before-provisioning.' ), NoExplicitNull, + OmitUnsetDefault, ] = False account_financials: Annotated[ StrictBool | None, @@ -2413,6 +2443,7 @@ class Account(AdCPBaseModel): description='Whether this seller exposes the `get_account_financials` task for querying account-level financial status (spend, credit, invoices). Acts as a **pre-call discriminator** — buyers MUST consult this field before issuing `get_account_financials`; when `false` (or absent), sellers MAY reject the call with an `UNSUPPORTED_FEATURE` / `OPERATION_NOT_SUPPORTED` error. Companion pattern to `creative.bills_through_adcp` (issue #2881) — both fields let buyers gate optional capability calls on a single declared boolean rather than probing for support. Only applicable to operator-billed accounts; sellers using buyer-billed flows omit or set to `false`.' ), NoExplicitNull, + OmitUnsetDefault, ] = False notifications: Annotated[ Notifications2 | Notifications3 | None, @@ -2441,6 +2472,7 @@ class Account(AdCPBaseModel): description='Whether this seller supports sandbox accounts for testing. Buyer-declared accounts use sandbox: true in sync_accounts or, for an unambiguous lazy-provisioning seller, in the natural-key account reference. Sellers with account_id namespaces expose sandbox accounts as pre-existing test accounts through list_accounts or supply them out-of-band. Requests using a sandbox account perform no real platform calls or spend.' ), NoExplicitNull, + OmitUnsetDefault, ] = False agent_onboarding: Annotated[ agent_onboarding_1.AgentOnboardingPosture | None, @@ -2489,6 +2521,7 @@ class CreativeSpecs(AdCPBaseModel): description="Level of VAST asset validation the seller performs at sync_creatives (including dry_run): 'structural' checks manifest shape and format requirements only and never inspects the VAST document; 'document' additionally parses the VAST document and can return VAST_PARSE_FAILED / VAST_VERSION_MISMATCH; 'wrapper' additionally resolves the wrapper chain and can return VAST_WRAPPER_DEPTH_EXCEEDED. Absent means 'structural'. See the VAST Validation section of the video channel documentation for the normative checks at each level." ), NoExplicitNull, + OmitUnsetDefault, ] = VastValidation.structural @@ -2545,6 +2578,7 @@ class SupportedFormat(AdCPBaseModel): min_length=1, ), NoExplicitNull, + OmitUnsetDefault, ] = [Operation.build] @@ -2565,6 +2599,7 @@ class Creative(AdCPBaseModel): description='When true, this agent hosts a creative library and supports list_creatives and creative_id references in build_creative. Creative agents with a library should also implement the accounts protocol (sync_accounts / list_accounts) so buyers can establish access.' ), NoExplicitNull, + OmitUnsetDefault, ] = False stable_creative_ids: Annotated[ StrictBool | None, @@ -2572,6 +2607,7 @@ class Creative(AdCPBaseModel): description="When true, this agent gives every creative that entered its library outside sync_creatives (seller UI or API, automation, or another system) a creative_id that is stable for the creative's lifetime and unique within the account, so a buyer connecting to an existing advertiser can reference those creatives in later requests. The agent MUST keep such IDs stable, MUST NOT reuse a creative_id a buyer has used on the account, and under sync_creatives delete_missing MUST NOT archive those creatives. Requires has_creative_library: true. When false or absent, sellers SHOULD follow the same rules but buyers MUST NOT rely on them." ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_revisions: Annotated[ StrictBool | None, @@ -2579,6 +2615,7 @@ class Creative(AdCPBaseModel): description='When true, this agent accepts buyer-assigned revision_id on sync_creatives, enforces immutable revision content, echoes accepted revision identity, returns it from list_creatives, and attributes delivered executions to it. Revision support does not imply revision history, rollback, or staged activation.' ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_assignment_merge: Annotated[ StrictBool | None, @@ -2586,6 +2623,7 @@ class Creative(AdCPBaseModel): description="When true, this agent accepts the merge operation in sync_creatives assignment_operations: a buyer declares that a seller-assigned creative assigned to a package is the same creative as its own, and the seller moves that assignment's serving object, approval, and delivery continuity to the buyer's creative_id. Requires has_creative_library: true and stable_creative_ids: true. When false or absent, the agent rejects merge with UNSUPPORTED_FEATURE and buyers MUST NOT send it." ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_assignment_trackers: Annotated[ StrictBool | None, @@ -2593,6 +2631,7 @@ class Creative(AdCPBaseModel): description="When true, this agent accepts trackers on creative assignments in create_media_buy, update_media_buy, and sync_creatives, fires them in addition to the creative's own trackers or rejects the assignment, enforces shared format tracker-slot limits, and returns the assignment-scoped tracker list in list_creatives and get_media_buys readback. Requires has_creative_library: true. When false or absent, the agent MUST reject the field with UNSUPPORTED_FEATURE and buyers MUST NOT send it." ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_assignment_landing_page: Annotated[ StrictBool | None, @@ -2600,6 +2639,7 @@ class Creative(AdCPBaseModel): description='When true, this agent accepts landing_page_url on creative assignments in create_media_buy, update_media_buy, and sync_creatives, serves that destination on the specified package or rejects the assignment, and returns the effective serving destination when unique in list_creatives and get_media_buys assignment readback. Requires has_creative_library: true. When false or absent, the agent MUST reject the field with UNSUPPORTED_FEATURE and buyers MUST NOT send it.' ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_generation: Annotated[ StrictBool | None, @@ -2607,6 +2647,7 @@ class Creative(AdCPBaseModel): description='When true, this agent can generate creatives from natural language briefs via build_creative. The buyer provides a message with creative direction, and the agent produces a manifest with generated assets. When false, build_creative only supports transformation or library retrieval.' ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_transformation: Annotated[ StrictBool | None, @@ -2614,6 +2655,7 @@ class Creative(AdCPBaseModel): description='When true, this agent can transform or resize existing canonical manifests via build_creative. The buyer supplies a creative_manifest and an advertised target_capability_id.' ), NoExplicitNull, + OmitUnsetDefault, ] = False representation_resolution: Annotated[ RepresentationResolution | None, @@ -2628,6 +2670,7 @@ class Creative(AdCPBaseModel): description='When true, this agent exposes account-scoped creative transformers via list_transformers (the creative analog of media-buy products) and accepts transformer_id + config on build_creative. Buyers SHOULD call list_transformers to discover available transformers, their typed config params (and account-scoped enumerable option values via expand_params), and pricing. When false or absent, the agent does not offer the transformer surface.' ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_refinement: Annotated[ StrictBool | None, @@ -2635,6 +2678,7 @@ class Creative(AdCPBaseModel): description="When true, this agent retains produced build_variant leaves for an agent-defined retention window and can re-build from one via build_creative's refine_from_build_variant_id — applying a natural-language instruction in message plus an optional config delta, returning new lineage-linked variants. A build-time agent capability independent of generation/transformation. When false or absent, refine_from_build_variant_id is rejected with UNSUPPORTED_FEATURE; buyers refine instead via the transform path (creative_manifest + message)." ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_spend_controls: Annotated[ StrictBool | None, @@ -2642,6 +2686,7 @@ class Creative(AdCPBaseModel): description='When true, build_creative honors a per-call `max_spend` ceiling (producing partial paid results and returning budget_status:"capped" + a BUDGET_CAP_REACHED advisory rather than overspending) AND supports mode:"estimate" dry-runs (a projected cost band, producing/billing nothing). When false or absent, max_spend / mode:estimate are rejected with UNSUPPORTED_FEATURE. Out-of-band billers (bills_through_adcp:false) have no AdCP cost truth to cap against, so this is meaningful only alongside bills_through_adcp:true.' ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_evaluator: Annotated[ StrictBool | None, @@ -2649,6 +2694,7 @@ class Creative(AdCPBaseModel): description="Experimental (x-status: experimental) — agents setting this true MUST also list `creative.evaluator` in `experimental_features`; the surface MAY change between 3.x releases with notice (see docs/reference/experimental-status). When true, build_creative accepts an advisory `evaluator` input (exemplars / agent_url, plus an optional `feature_requirement[]` gate, a `rank_by` ordering, and an allowlisted `feature_agent` pointer). Feature discovery uses this response's governance.creative_features catalog: rank_by, feature_requirement, and eval.features[] all share the same creative-feature vocabulary as get_creative_features. A seller's house evaluator is an agent it lists in `creative_policy.accepted_verifiers[]`, named via `agent_url`; its emitted feature_ids still come from this catalog. The evaluator populates a per-leaf `eval` block of creative-feature values (creative-feature-result[], the same shape get_creative_features returns) on BuildCreativeVariantSuccess leaves, which is what the recommended/rank it sets on the best_of_n axis are computed over. The agent runs a gate-then-rank pipeline over its best_of_n exploration: it evaluates each leaf, DROPS leaves failing `feature_requirement[]` from its recommended survivors, then orders survivors by `rank_by`. The gate is internal pruning of which leaves the agent recommends/returns from its own exploration — it never blocks an already-produced billable leaf: what is produced and billed is governed by max_variants/max_creatives/max_spend, not the evaluator. When the evaluator names an external agent, it MUST appear in `creative_policy.accepted_verifiers[]` (off-list → EVALUATOR_AGENT_NOT_ACCEPTED), and the producing agent authenticates the outbound evaluator call on the transport. Evaluator credentials and caller-supplied trust material MUST NOT be passed in the build_creative payload; credential- or trust-material payload keys should be rejected with CREDENTIAL_IN_ARGS. When false or absent, the `evaluator` input is ignored and no `eval` block is emitted." ), NoExplicitNull, + OmitUnsetDefault, ] = False refinable_retention_seconds: Annotated[ SchemaInt | None, @@ -2692,6 +2738,7 @@ class Creative(AdCPBaseModel): description='When true, this creative agent bills through the AdCP rate-card surface: list_creatives returns pricing_options when include_pricing=true with an authenticated account, build_creative populates pricing_option_id and vendor_cost on the response, and report_usage accepts records against the rate card. When false or absent, the agent bills out of band (flat license, SaaS contract, bundled enterprise agreement) and buyers should skip pricing fields and tolerate report_usage returning accepted: 0 with errors carrying BILLING_OUT_OF_BAND. A pre-call discriminator so buyer agents can route across many creative agents without first establishing an account to probe pricing.' ), NoExplicitNull, + OmitUnsetDefault, ] = False canonical_catalog_version: Annotated[ str | None, @@ -2806,6 +2853,7 @@ class MediaBuy(AdCPBaseModel): min_length=1, ), NoExplicitNull, + OmitUnsetDefault, ] = [BuyingMode.brief] measurement_terms_acceptance: Annotated[ StrictBool | None, @@ -2813,6 +2861,7 @@ class MediaBuy(AdCPBaseModel): description="Whether this seller can accept the default measurement_terms it advertises on a product. A value of true means the seller can return a product carrying measurement_terms for a measurement-specific brief and accept those terms unchanged on a package for that product. This opts the seller into conformance scenarios that discover and replay the product's own terms. False or absent means acceptance is outside the seller's advertised scope, but the seller must still reject unsupported terms with TERMS_REJECTED rather than being graded on an acceptance path it did not claim." ), NoExplicitNull, + OmitUnsetDefault, ] = False availability_horizon: Annotated[ StrictBool | None, @@ -2820,6 +2869,7 @@ class MediaBuy(AdCPBaseModel): description='Whether this seller supports flexible-window availability discovery: parsing offer_filters.availability_horizon and answering with time-dimensioned forecast points that carry availability_status. Sellers declaring true MUST apply the full window contract — half-open non-overlapping windows that partition the requested horizon (or signal gaps via incomplete[]), with availability_status computed from all booking eligibility constraints, not only competing holds. false or absent means flexible-window support is unknown: buyers SHOULD use exact start_date/end_date filtering, and sellers MAY ignore the field or reject it. Conformance storyboards gate flexible-window checks on this declaration.' ), NoExplicitNull, + OmitUnsetDefault, ] = False lifecycle_tools: Annotated[ list[LifecycleTool] | None, @@ -2879,6 +2929,7 @@ class MediaBuy(AdCPBaseModel): description='Conformance declaration that this seller supports proposals through either the compact request/refine/finalize lifecycle or the legacy get_products facade. accept_proposal, or the create_media_buy compatibility facade, consumes a finalized committed proposal_id before expires_at.' ), NoExplicitNull, + OmitUnsetDefault, ] = False supports_exclusive_product_ids: Annotated[ StrictBool | None, @@ -2886,6 +2937,7 @@ class MediaBuy(AdCPBaseModel): description='Experimental (media_buy.delegated_composition). Declares that this seller honors criteria.product_ids_exclusive: when a request sets it, the listed product_ids are the complete composable universe, and every proposal purchase, echoed product, and seller-selected signal resolves within them — a brief unsatisfiable within the universe returns rejected (request_proposals) or constraint_unsatisfiable (refine_proposals), never off-universe results. Refinements inherit the universe and may only narrow it. Buyers MUST NOT send the flag unless this is true. Sellers that do not declare this capability MUST NOT silently ignore the flag on request_proposals or refine_proposals; they reject the request at task level with UNSUPPORTED_FEATURE. Primary use is delegated composition, where the requesting platform owns the sellable catalog and the responding agent supplies only the composition judgment.' ), NoExplicitNull, + OmitUnsetDefault, ] = False outcome_target: Annotated[ StrictBool | None, @@ -2893,6 +2945,7 @@ class MediaBuy(AdCPBaseModel): description="Whether this seller supports reverse-forecast planning: parsing criteria.outcome_target (a compact goal — a forecastable-metric delivery metric, an event-type conversion event, or a vendor_metric — plus a desired volume, a cost_per target, or both) and planning against it, answering with total_budget_guidance and forecasts whose points carry the goal's key in metrics, or its (vendor, metric_id) in vendor_metric_values[] for a vendor_metric goal; see outcome-target.json for cost_per answers and rejections. This flag does not distinguish sellers that plan cost_per or vendor_metric goals; buyers rely on the negotiated adcp_version together with features.bidding_policy, conversion_tracking.supported_targets, and vendor_metric_optimization. false or absent means support is unknown: buyers SHOULD express outcome goals in brief prose instead, and sellers reject a structured outcome_target on proposal requests with UNSUPPORTED_FEATURE rather than silently ignoring it (list_products ignores it for every seller)." ), NoExplicitNull, + OmitUnsetDefault, ] = False governance_aware: Annotated[ StrictBool | None, @@ -2900,6 +2953,7 @@ class MediaBuy(AdCPBaseModel): description='Compatibility claim used by existing media-buy conformance runners. A value of true corresponds only to online governance consultation for create_media_buy, the historically graded surface. Agents use adcp.governance_enforcement for explicit task-scoped claims, including update_media_buy and cross-role signed-context enforcement.' ), NoExplicitNull, + OmitUnsetDefault, ] = False propagation_surfaces: Annotated[ list[PropagationSurface] | None, @@ -2908,6 +2962,7 @@ class MediaBuy(AdCPBaseModel): min_length=1, ), NoExplicitNull, + OmitUnsetDefault, ] = [PropagationSurface.snapshot] creative_approval_mode: Annotated[ CreativeApprovalMode | None, diff --git a/src/adcp/types/domains/protocol/get_task_status_request.py b/src/adcp/types/domains/protocol/get_task_status_request.py index 7680753d1..c65605fd2 100644 --- a/src/adcp/types/domains/protocol/get_task_status_request.py +++ b/src/adcp/types/domains/protocol/get_task_status_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: protocol/get_task_status_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import account_ref @@ -35,6 +35,7 @@ class GetTaskStatusRequest(AdcpRequest, AdcpVersionEnvelope): description='Include full conversation history for this task (may increase response size)' ), NoExplicitNull, + OmitUnsetDefault, ] = False include_result: Annotated[ StrictBool | None, @@ -42,6 +43,7 @@ class GetTaskStatusRequest(AdcpRequest, AdcpVersionEnvelope): description="Include the task's canonical terminal result payload when one exists. Defaults to false for lightweight status-only polls. When true, sellers MUST include result for completed, failed, or rejected terminal tasks when that task produced a terminal artifact; canceled tasks may have no result. The legacy singular error field remains a convenience for failed tasks but does not replace the canonical terminal result." ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/protocol/list_tasks_request.py b/src/adcp/types/domains/protocol/list_tasks_request.py index 3c024126a..6195f6552 100644 --- a/src/adcp/types/domains/protocol/list_tasks_request.py +++ b/src/adcp/types/domains/protocol/list_tasks_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: protocol/list_tasks_request.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireDatetime from pydantic import ConfigDict, Field, StrictBool from ..core import account_ref @@ -104,11 +104,14 @@ class Sort(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) - field: Annotated[Field1 | None, Field(description='Field to sort by'), NoExplicitNull] = ( - Field1.created_at - ) + field: Annotated[ + Field1 | None, Field(description='Field to sort by'), NoExplicitNull, OmitUnsetDefault + ] = Field1.created_at direction: Annotated[ - sort_direction.SortDirection | None, Field(description='Sort direction'), NoExplicitNull + sort_direction.SortDirection | None, + Field(description='Sort direction'), + NoExplicitNull, + OmitUnsetDefault, ] = sort_direction.SortDirection.desc @@ -134,6 +137,7 @@ class ListTasksRequest(AdcpRequest, AdcpVersionEnvelope): description='Include full conversation history for each task (may significantly increase response size)' ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/protocol/sync_agent_notification_configs_request.py b/src/adcp/types/domains/protocol/sync_agent_notification_configs_request.py index 8085fcec4..d8fcfdbdb 100644 --- a/src/adcp/types/domains/protocol/sync_agent_notification_configs_request.py +++ b/src/adcp/types/domains/protocol/sync_agent_notification_configs_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: protocol/sync_agent_notification_configs_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import agent_notification_config @@ -43,6 +43,7 @@ class SyncAgentNotificationConfigsRequest(AdcpRequest, AdcpVersionEnvelope): description='When true, validate the replacement and report what would be persisted without applying it or sending endpoint proof-of-control challenges.' ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/protocol/sync_principal_request.py b/src/adcp/types/domains/protocol/sync_principal_request.py index b6d09c524..1704a2423 100644 --- a/src/adcp/types/domains/protocol/sync_principal_request.py +++ b/src/adcp/types/domains/protocol/sync_principal_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: protocol/sync_principal_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import agent_notification_config, agent_reporting_destination @@ -89,6 +89,7 @@ class SyncPrincipalRequest(AdcpRequest, AdcpVersionEnvelope): description='Validate the proposed replacements and report the would-be action without persisting them, issuing durable identifiers or grants, or sending endpoint proof challenges.' ), NoExplicitNull, + OmitUnsetDefault, ] = False context: Annotated[context_1.ContextObject | None, NoExplicitNull] = None ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/registries/v1_canonical_mapping.py b/src/adcp/types/domains/registries/v1_canonical_mapping.py index e6917f58a..777e055a8 100644 --- a/src/adcp/types/domains/registries/v1_canonical_mapping.py +++ b/src/adcp/types/domains/registries/v1_canonical_mapping.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: registries/v1_canonical_mapping.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictBool @@ -76,6 +76,7 @@ class ParameterMapping(AdCPBaseModel): description='Value transform applied during forwarding. identity copies the value; singleton_array wraps it in a one-element array.' ), NoExplicitNull, + OmitUnsetDefault, ] = Transform.identity @@ -110,6 +111,7 @@ class Mapping(AdCPBaseModel): description='When true, this mapping is retained for backward-compatibility but should not be used for new mappings. SDKs SHOULD emit lint warnings when matching a deprecated entry.' ), NoExplicitNull, + OmitUnsetDefault, ] = False notes: Annotated[ str | None, diff --git a/src/adcp/types/domains/signals/activate_signal_request.py b/src/adcp/types/domains/signals/activate_signal_request.py index 5b162b9e7..1c0c9b218 100644 --- a/src/adcp/types/domains/signals/activate_signal_request.py +++ b/src/adcp/types/domains/signals/activate_signal_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: signals/activate_signal_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field from ..core import account_ref @@ -34,6 +34,7 @@ class ActivateSignalRequest(AdcpRequest, AdcpVersionEnvelope): description="Whether to activate or deactivate the signal. Deactivating removes the segment from downstream platforms, required when campaigns end to comply with data governance policies (GDPR, CCPA). Defaults to 'activate' when omitted." ), NoExplicitNull, + OmitUnsetDefault, ] = Action.activate signal_agent_segment_id: Annotated[ str, diff --git a/src/adcp/types/domains/signals/get_signals_request.py b/src/adcp/types/domains/signals/get_signals_request.py index eb1a92ce7..a05449d63 100644 --- a/src/adcp/types/domains/signals/get_signals_request.py +++ b/src/adcp/types/domains/signals/get_signals_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: signals/get_signals_request.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field @@ -83,6 +83,7 @@ class GetSignalsRequest(AdcpRequest, AdcpVersionEnvelope): description="Declares caller intent for this request. 'brief' (default): semantic discovery — signal_spec, signal_refs, or legacy signal_ids is required and the agent performs inference/RAG. 'wholesale': raw wholesale signals feed enumeration — signal_spec, signal_refs, and signal_ids MUST NOT be provided and the agent returns its full priced signals feed, paginated, scoped by filters/account/destinations/countries when present. Sellers receiving requests from pre-v3.1 clients without discovery_mode MUST default to 'brief'. Timing semantics: 'wholesale' is a wholesale signals feed read — agents SHOULD respond synchronously and MUST NOT route a 'wholesale' request through the async/Submitted arm; partial completion is signalled via the response's incomplete[] field, not via a task-handoff envelope. Agents that do not implement wholesale enumeration MAY return INVALID_REQUEST for wholesale calls; callers SHOULD probe via get_adcp_capabilities (signals.discovery_modes) first." ), NoExplicitNull, + OmitUnsetDefault, ] = DiscoveryMode.brief account: Annotated[ account_ref.AccountReference | None, diff --git a/src/adcp/types/domains/signals/get_signals_response.py b/src/adcp/types/domains/signals/get_signals_response.py index 0ae0ea4d8..0bb103231 100644 --- a/src/adcp/types/domains/signals/get_signals_response.py +++ b/src/adcp/types/domains/signals/get_signals_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: signals/get_signals_response.json -# timestamp: 2026-10-26T23:54:48+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -10,7 +10,7 @@ from collections.abc import Sequence from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl, WireDatetime, WireEmail +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl, WireDatetime, WireEmail from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool, StrictFloat diff --git a/src/adcp/types/domains/sponsored_intelligence/si_capabilities.py b/src/adcp/types/domains/sponsored_intelligence/si_capabilities.py index 43b704b4c..ce1e9dc80 100644 --- a/src/adcp/types/domains/sponsored_intelligence/si_capabilities.py +++ b/src/adcp/types/domains/sponsored_intelligence/si_capabilities.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: sponsored_intelligence/si_capabilities.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt from pydantic import ConfigDict, Field, StrictBool @@ -59,6 +59,7 @@ class Modalities(AdCPBaseModel): StrictBool | None, Field(description='Pure text exchange - the baseline modality'), NoExplicitNull, + OmitUnsetDefault, ] = True voice: Annotated[ StrictBool | Voice | None, @@ -116,7 +117,10 @@ class A2ui(AdCPBaseModel): extra='allow', ) supported: Annotated[ - StrictBool | None, Field(description='Supports A2UI surface rendering'), NoExplicitNull + StrictBool | None, + Field(description='Supports A2UI surface rendering'), + NoExplicitNull, + OmitUnsetDefault, ] = False catalogs: Annotated[ list[str] | None, @@ -145,4 +149,5 @@ class SiCapabilities(AdCPBaseModel): StrictBool | None, Field(description='Supports MCP Apps for rendering A2UI surfaces in iframes'), NoExplicitNull, + OmitUnsetDefault, ] = False diff --git a/src/adcp/types/domains/sponsored_intelligence/si_get_offering_request.py b/src/adcp/types/domains/sponsored_intelligence/si_get_offering_request.py index 34faf3140..fa0d19482 100644 --- a/src/adcp/types/domains/sponsored_intelligence/si_get_offering_request.py +++ b/src/adcp/types/domains/sponsored_intelligence/si_get_offering_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: sponsored_intelligence/si_get_offering_request.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated -from adcp.types.base import NoExplicitNull +from adcp.types.base import NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictBool from ..core import context as context_1 @@ -35,10 +35,12 @@ class SiGetOfferingRequest(AdcpRequest, AdcpVersionEnvelope): StrictBool | None, Field(description='Whether to include matching products in the response'), NoExplicitNull, + OmitUnsetDefault, ] = False product_limit: Annotated[ SchemaInt | None, Field(description='Maximum number of matching products to return', ge=1, le=50), NoExplicitNull, + OmitUnsetDefault, ] = 5 ext: Annotated[ext_1.ExtensionObject | None, NoExplicitNull] = None diff --git a/src/adcp/types/domains/trust/v1/trust.py b/src/adcp/types/domains/trust/v1/trust.py index e4e5bb6d0..297066724 100644 --- a/src/adcp/types/domains/trust/v1/trust.py +++ b/src/adcp/types/domains/trust/v1/trust.py @@ -1,15 +1,15 @@ # generated by datamodel-codegen: # filename: trust/v1/trust.json -# timestamp: 2026-10-27T00:56:00+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl, WireDatetime +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl, WireDatetime from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel, StrictBool, StringConstraints +from pydantic import ConfigDict, Field, StrictBool, StringConstraints from ...core import buyer_onboarding_posture, identity_relying_party from ...enums import right_type, right_use @@ -26,7 +26,7 @@ class Domain(ScalarStr): } -class HttpsUrl(RootModel[WireAnyUrl]): +class HttpsUrl(AdCPRootModel[WireAnyUrl]): root: Annotated[ WireAnyUrl, Field( @@ -94,7 +94,7 @@ class GrantSource1(StrEnum): ] -class OpenProfiles(RootModel[dict[Annotated[str, StringConstraints(pattern=r'^[a-z0-9-]+$')], dict[str, Any]]]): +class OpenProfiles(AdCPRootModel[dict[Annotated[str, StringConstraints(pattern=r'^[a-z0-9-]+$')], dict[str, Any]]]): root: Annotated[ dict[Annotated[str, StringConstraints(pattern=r'^[a-z0-9-]+$')], dict[str, Any]], Field( @@ -357,6 +357,7 @@ class TrustJson2(AdCPBaseModel): description="When true, grants[] is exhaustive for every scope it names: relying parties MUST reject acts-for claims no active grant covers. When false or absent, an uncovered claim is at the relying party's discretion." ), NoExplicitNull, + OmitUnsetDefault, ] = False acknowledgements: Annotated[ list[Acknowledgement] | None, diff --git a/src/adcp/types/domains/trusted_match/context_match_request.py b/src/adcp/types/domains/trusted_match/context_match_request.py index abcea5797..1dee0457a 100644 --- a/src/adcp/types/domains/trusted_match/context_match_request.py +++ b/src/adcp/types/domains/trusted_match/context_match_request.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: trusted_match/context_match_request.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl, WireUuid +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl, WireUuid from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field @@ -78,6 +78,7 @@ class ContextSignals(AdCPBaseModel): description="Organization that defines the topic taxonomy. Use 'iab' for IAB Content Taxonomy. Publishers may use other values for custom taxonomies." ), NoExplicitNull, + OmitUnsetDefault, ] = 'iab' taxonomy_id: Annotated[ SchemaInt | None, @@ -85,6 +86,7 @@ class ContextSignals(AdCPBaseModel): description='Taxonomy version within the source. For IAB, follows the AdCOM cattax enum: 7 = Content Taxonomy 3.0. Default: 7.' ), NoExplicitNull, + OmitUnsetDefault, ] = 7 sentiment: Annotated[ Sentiment | None, Field(description='Content sentiment classification.'), NoExplicitNull @@ -207,6 +209,7 @@ class ContextMatchRequest(AdcpVersionEnvelope): description='TMP protocol version. Allows receivers to handle semantic differences across versions.' ), NoExplicitNull, + OmitUnsetDefault, ] = '1.0' request_id: Annotated[ str, diff --git a/src/adcp/types/domains/trusted_match/context_match_response.py b/src/adcp/types/domains/trusted_match/context_match_response.py index 73f7a2253..12884f1f1 100644 --- a/src/adcp/types/domains/trusted_match/context_match_response.py +++ b/src/adcp/types/domains/trusted_match/context_match_response.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: trusted_match/context_match_response.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull -from pydantic import ConfigDict, Field, RootModel, StringConstraints +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull +from pydantic import ConfigDict, Field, StringConstraints from ..core.protocol_envelope import ProtocolEnvelope from ..core.version_envelope import AdcpVersionEnvelope @@ -38,7 +38,7 @@ class TargetingKv(AdCPBaseModel): value: Annotated[str, Field(description='Targeting value preserved unchanged by the router.')] -class TargetingKvs(RootModel[list[TargetingKv]]): +class TargetingKvs(AdCPRootModel[list[TargetingKv]]): root: Annotated[ list[TargetingKv], Field( diff --git a/src/adcp/types/domains/trusted_match/identity_match_request.py b/src/adcp/types/domains/trusted_match/identity_match_request.py index f094c8294..b1d5ba3fe 100644 --- a/src/adcp/types/domains/trusted_match/identity_match_request.py +++ b/src/adcp/types/domains/trusted_match/identity_match_request.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: trusted_match/identity_match_request.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Any, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, WireAnyUrl, WireDatetime +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, WireAnyUrl, WireDatetime from pydantic import ConfigDict, Field, StrictBool from ..core import brand_ref @@ -180,6 +180,7 @@ class IdentityMatchRequest(AdcpVersionEnvelope): description='TMP protocol version. Allows receivers to handle semantic differences across versions.' ), NoExplicitNull, + OmitUnsetDefault, ] = '1.0' request_id: Annotated[ str, diff --git a/src/adcp/types/domains/trusted_match/offer_price.py b/src/adcp/types/domains/trusted_match/offer_price.py index 3496bb89f..de10c36a4 100644 --- a/src/adcp/types/domains/trusted_match/offer_price.py +++ b/src/adcp/types/domains/trusted_match/offer_price.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: trusted_match/offer_price.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated -from adcp.types.base import AdCPBaseModel, NoExplicitNull +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault from pydantic import ConfigDict, Field, StrictFloat @@ -30,5 +30,6 @@ class OfferPrice(AdCPBaseModel): str | None, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$'), NoExplicitNull, + OmitUnsetDefault, ] = 'USD' model: Annotated[Model, Field(description='Pricing model for this offer')] diff --git a/src/adcp/types/domains/trusted_match/provider_context_match_response.py b/src/adcp/types/domains/trusted_match/provider_context_match_response.py index c15bae21e..0cc49f8c1 100644 --- a/src/adcp/types/domains/trusted_match/provider_context_match_response.py +++ b/src/adcp/types/domains/trusted_match/provider_context_match_response.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: trusted_match/provider_context_match_response.json -# timestamp: 2026-10-21T11:05:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt -from pydantic import ConfigDict, Field, RootModel +from adcp.types.base import AdCPBaseModel, AdCPRootModel, NoExplicitNull, SchemaInt +from pydantic import ConfigDict, Field from ..core.protocol_envelope import ProtocolEnvelope from ..core.version_envelope import AdcpVersionEnvelope @@ -27,7 +27,7 @@ class TargetingKv(AdCPBaseModel): value: Annotated[str, Field(description='Targeting value that the router preserves unchanged.')] -class TargetingKvs(RootModel[list[TargetingKv]]): +class TargetingKvs(AdCPRootModel[list[TargetingKv]]): root: Annotated[ list[TargetingKv], Field( diff --git a/src/adcp/types/domains/trusted_match/provider_registration.py b/src/adcp/types/domains/trusted_match/provider_registration.py index 6cbb09f08..070836302 100644 --- a/src/adcp/types/domains/trusted_match/provider_registration.py +++ b/src/adcp/types/domains/trusted_match/provider_registration.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: trusted_match/provider_registration.json -# timestamp: 2026-10-25T19:32:54+00:00 +# timestamp: 2026-10-28T15:43:47+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from typing import Annotated, Literal -from adcp.types.base import AdCPBaseModel, NoExplicitNull, SchemaInt, WireAnyUrl, WireUuid +from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault, SchemaInt, WireAnyUrl, WireUuid from adcp.types._scalar import ScalarStr from pydantic import ConfigDict, Field, StrictBool, field_validator, model_validator @@ -97,6 +97,7 @@ class TmpProviderRegistration1(AdCPBaseModel): le=5000, ), NoExplicitNull, + OmitUnsetDefault, ] = 50 priority: Annotated[ SchemaInt | None, @@ -105,6 +106,7 @@ class TmpProviderRegistration1(AdCPBaseModel): ge=0, ), NoExplicitNull, + OmitUnsetDefault, ] = 0 tmpx_slots: Annotated[ list[TmpxSlot] | None, @@ -121,6 +123,7 @@ class TmpProviderRegistration1(AdCPBaseModel): description='Provider lifecycle status. Active providers receive requests. Inactive providers are skipped entirely. Draining providers stop receiving new requests but in-flight requests complete normally.' ), NoExplicitNull, + OmitUnsetDefault, ] = Status.active @@ -201,6 +204,7 @@ class TmpProviderRegistration2(AdCPBaseModel): le=5000, ), NoExplicitNull, + OmitUnsetDefault, ] = 50 priority: Annotated[ SchemaInt | None, @@ -209,6 +213,7 @@ class TmpProviderRegistration2(AdCPBaseModel): ge=0, ), NoExplicitNull, + OmitUnsetDefault, ] = 0 tmpx_slots: Annotated[ list[TmpxSlot] | None, @@ -225,6 +230,7 @@ class TmpProviderRegistration2(AdCPBaseModel): description='Provider lifecycle status. Active providers receive requests. Inactive providers are skipped entirely. Draining providers stop receiving new requests but in-flight requests complete normally.' ), NoExplicitNull, + OmitUnsetDefault, ] = Status.active diff --git a/tests/conformance/_round_trip.py b/tests/conformance/_round_trip.py index f6d4445b8..f74765c22 100644 --- a/tests/conformance/_round_trip.py +++ b/tests/conformance/_round_trip.py @@ -37,12 +37,13 @@ from functools import cache from pathlib import Path from types import UnionType -from typing import Annotated, Any, Union, get_origin +from typing import Annotated, Any, Literal, Union, get_args, get_origin import rfc8785 -from pydantic import TypeAdapter, ValidationError +from pydantic import BaseModel, RootModel, TypeAdapter, ValidationError import adcp.types._eager # noqa: F401 (see below) +from adcp.types.base import is_omit_unset_default from adcp.validation.schema_loader import ( _ensure_state, _make_ref_resolver, @@ -149,6 +150,11 @@ "the model emits a field the document omitted: a schema default the " "generator wrote into the field, or a value the model computes" ), + "discriminator_emitted": ( + "the model emits a single-value Literal the document omitted: the " + "document was routed to an arm that requires a discriminator it does " + "not carry, so the arm's injected default appears (arm routing, #1414)" + ), "null_dropped": ("an explicit null the schema admits is dropped by the dump's exclude_none"), "field_dropped": ( "a property the document carried is absent after the round trip: an " @@ -399,8 +405,6 @@ def dump_as_received(grader: Any, parsed: Any) -> Any: way); ``by_alias`` writes the wire name. A union alias root gets the same settings through its adapter. """ - if hasattr(parsed, "model_dump_as_received"): - return parsed.model_dump_as_received() kwargs: dict[str, Any] = dict( mode="json", exclude_unset=True, exclude_none=False, by_alias=True ) @@ -722,7 +726,72 @@ def round_trip( document.schema, document.name, frozenset({type(exc).__name__}), str(exc)[:200] ) dumped = dump_json(model, parsed) if not as_received else dump_as_received(model, parsed) - return differences(document.schema, document.name, document.payload, dumped), None + found = differences(document.schema, document.name, document.payload, dumped) + return [_reclassify(parsed, d) for d in found], None + + +def _reclassify(parsed: Any, difference: Difference) -> Difference: + """Name an emitted injected discriminator as arm routing, not as a default. + + A single-value ``Literal`` field the generator gave a default is a + discriminator an arm requires; when the document omitted it and the model + still emits it, the document was routed to an arm it does not satisfy. + That is ``discriminator_emitted``, a routing defect (#1414), not a schema + default (#1613). A field carrying ``OmitUnsetDefault`` is a schema + default and keeps its class. + """ + if difference.kind != "default_emitted": + return difference + parts = [part for part in difference.pointer.split("/") if part] + if not parts: + return difference + owner: Any = parsed + for part in parts[:-1]: + owner = _step(owner, part) + if owner is None: + return difference + if isinstance(owner, RootModel): + owner = owner.root + if not isinstance(owner, BaseModel): + return difference + name = _field_name(type(owner), parts[-1]) + if name is None: + return difference + field = type(owner).model_fields[name] + if is_omit_unset_default(field): + return difference + if get_origin(field.annotation) is Literal and len(get_args(field.annotation)) == 1: + return Difference( + difference.schema, + difference.document, + difference.pointer, + "discriminator_emitted", + difference.before, + difference.after, + ) + return difference + + +def _step(owner: Any, part: str) -> Any: + if isinstance(owner, RootModel): + owner = owner.root + if isinstance(owner, list): + return owner[int(part)] if part.isdigit() and int(part) < len(owner) else None + if isinstance(owner, dict): + return owner.get(part) + if isinstance(owner, BaseModel): + name = _field_name(type(owner), part) + return getattr(owner, name) if name is not None else None + return None + + +def _field_name(model: type[BaseModel], key: str) -> str | None: + if key in model.model_fields: + return key + for name, info in model.model_fields.items(): + if key in (info.alias, info.serialization_alias): + return name + return None def grader_for(document: Document) -> Any: diff --git a/tests/conformance/reporting/_late_account_server.py b/tests/conformance/reporting/_late_account_server.py index 46acbb24c..1e9382096 100644 --- a/tests/conformance/reporting/_late_account_server.py +++ b/tests/conformance/reporting/_late_account_server.py @@ -217,6 +217,8 @@ def main(): "grain": "row", "primary_keys": ["row_id"], "canonicalization_id": key.canonicalization.canonicalization_id, + "canonicalization_contract_version": "1.0", + "canonicalization_media_type": "application/vnd.adcp.reporting-canonicalization+json", "canonicalization_uri": key.canonicalization.canonicalization_uri, "canonicalization_sha256": key.canonicalization.canonicalization_sha256, } diff --git a/tests/conformance/reporting/_production_support.py b/tests/conformance/reporting/_production_support.py index 28bbae998..6bd44089d 100644 --- a/tests/conformance/reporting/_production_support.py +++ b/tests/conformance/reporting/_production_support.py @@ -570,6 +570,10 @@ async def production_harness( "grain": "row", "primary_keys": ["row_id"], "canonicalization_id": key.canonicalization.canonicalization_id, + # The consumer_receipt arm requires the two canonicalization + # constants too; a dump no longer materializes them unset (#1613). + "canonicalization_contract_version": "1.0", + "canonicalization_media_type": "application/vnd.adcp.reporting-canonicalization+json", "canonicalization_uri": key.canonicalization.canonicalization_uri, "canonicalization_sha256": key.canonicalization.canonicalization_sha256, } diff --git a/tests/conformance/reporting/test_reporting_production_service_factory.py b/tests/conformance/reporting/test_reporting_production_service_factory.py index 822412377..8158e7500 100644 --- a/tests/conformance/reporting/test_reporting_production_service_factory.py +++ b/tests/conformance/reporting/test_reporting_production_service_factory.py @@ -222,6 +222,10 @@ async def factory_harness( "grain": "row", "primary_keys": ["row_id"], "canonicalization_id": key.canonicalization.canonicalization_id, + "canonicalization_contract_version": "1.0", + "canonicalization_media_type": ( + "application/vnd.adcp.reporting-canonicalization+json" + ), "canonicalization_uri": key.canonicalization.canonicalization_uri, "canonicalization_sha256": key.canonicalization.canonicalization_sha256, }, diff --git a/tests/conformance/test_wire_round_trip.py b/tests/conformance/test_wire_round_trip.py index e143de28c..8a5440855 100644 --- a/tests/conformance/test_wire_round_trip.py +++ b/tests/conformance/test_wire_round_trip.py @@ -25,7 +25,8 @@ the classes named in :data:`ACCEPTED`, each with the reason it remains and the issue that tracks it; the set only shrinks, and a class that stops occurring must be deleted from it. A class outside ``ACCEPTED`` — a rewritten URL, -datetime, UUID or email, a changed value, a changed shape — fails anywhere. +datetime, UUID or email, an emitted default, a dropped null, a Python-named +key, a changed value, a changed shape — fails anywhere. """ from __future__ import annotations @@ -42,13 +43,15 @@ #: is a regression. A class that stops occurring is deleted — this set only #: shrinks. ACCEPTED: dict[str, str] = { - # The generator writes a schema ``default`` into the field and the dump - # emits every field, so a document that omitted a defaulted property gains - # it. "Omitted" and "present with the default" read the same to a schema - # validator; they hash differently. #1613 proposes leaving an unset - # optional default off the dump; until then, dump as received - # (``exclude_unset=True``) to recover the bytes. - "default_emitted": "the generator materializes schema defaults on dump (#1613)", + # A document routed to an arm whose required discriminator it does not + # carry gains that arm's injected single-value ``Literal``. The generated + # arm classes merge the root's properties, so a document valid under a + # sibling arm validates under the first arm that accepts it. Arm routing + # is #1414's domain (closed by #1522's construction dispatch); a schema + # default the document omitted is no longer emitted (#1613). + "discriminator_emitted": ( + "a document routed to an arm it does not satisfy gains the arm's discriminator (#1414)" + ), # ``extra='ignore'`` drops a property the pinned model does not declare, # and a union arm declares only its own properties while the schema's arm # admits its siblings'. No digest-covered schema is affected at this pin; @@ -56,26 +59,10 @@ "field_dropped": "unknown and sibling-arm properties are dropped (#1614)", } -#: Schemas whose bytes the protocol binds. Under ``model_dump_as_received`` -#: no difference of any class; under the default dump no rewrite, no dropped -#: field, no alias and no dropped null -- only, for the schemas listed in -#: :data:`BOUND_DEFAULTS`, a schema default the document omitted. +#: Schemas whose bytes the protocol binds: no difference of any class, under +#: the default dump and under the explicit as-received settings alike. BOUND = sorted(rt.DIGEST_COVERED | rt.ECHO_COVERED) -#: Bound schemas whose *default* dump still materializes a schema default the -#: document omitted (#1613): ``commercial_terms`` reaches ``targeting.json`` -#: (``age_restriction.verification_required``), so ``terms_digest`` recomputed -#: over ``model_dump(mode="json")`` mismatches whenever the seller left it -#: out. ``model_dump_as_received`` recovers the bytes, and that is the -#: verification path until #1613 lands. The set only shrinks; a bound schema -#: joining it is a regression. -BOUND_DEFAULTS = frozenset( - { - "core/canonical-proposal.json", - "media-buy/commercial-terms.json", - } -) - #: Schemas with at least one schema-valid document the generated model refuses #: (fails validation). Refusing is the schema/model parity suite's domain, not #: this one's; the set is recorded so a refusal cannot silently remove a schema @@ -158,25 +145,14 @@ def test_bound_object_round_trips_byte_for_byte_as_received(schema: str) -> None @pytest.mark.parametrize("schema", BOUND) -def test_bound_object_is_never_rewritten_by_the_default_dump(schema: str) -> None: - """The default dump of a bound object rewrites nothing; at most it adds a declared default.""" +def test_bound_object_round_trips_byte_for_byte_by_default(schema: str) -> None: + """The default dump of a digest- or echo-covered object is the document.""" graded, _refusals = _graded(schema) changed = [d for _, differences in graded for d in differences] - rewritten = [d for d in changed if d.kind != "default_emitted"] - assert not rewritten, "\n".join( - [f"{len(rewritten)} rewrite(s) in a digest- or echo-covered object ({schema}):"] - + [f" {d.describe()}" for d in rewritten[:20]] + assert not changed, "\n".join( + [f"{len(changed)} difference(s) in a digest- or echo-covered object ({schema}):"] + + [f" {d.describe()}" for d in changed[:20]] ) - defaults = [d for d in changed if d.kind == "default_emitted"] - if schema in BOUND_DEFAULTS: - assert ( - defaults - ), f"{schema} no longer emits an omitted default; delete it from BOUND_DEFAULTS" - else: - assert not defaults, "\n".join( - [f"{schema} now emits a default the document omitted (#1613):"] - + [f" {d.describe()}" for d in defaults[:20]] - ) @pytest.mark.parametrize("schema", SCHEMAS) diff --git a/tests/test_canonical_formats_v1_to_v2.py b/tests/test_canonical_formats_v1_to_v2.py index 7c2fd2a3f..660186f8c 100644 --- a/tests/test_canonical_formats_v1_to_v2.py +++ b/tests/test_canonical_formats_v1_to_v2.py @@ -109,10 +109,12 @@ def test_seller_slots_override_registry_retina_slot_contract() -> None: "asset_group_id": "seller_image", "asset_type": "image", "required": True, - "consumed_for_production": True, } ], } + # ``consumed_for_production`` reads its schema default (true) and, unset, + # stays off the wire (SDK 10, #1613). + assert result.declaration.params["slots"][0].get("consumed_for_production") is None assert result.advisories == [] diff --git a/tests/test_client.py b/tests/test_client.py index 4a4cfe3a7..f0d92aad9 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -238,7 +238,7 @@ async def test_get_products(): # Verify adapter method was called mock_get.assert_called_once_with( - {"brief": "test campaign", "buying_mode": "brief", "include_collections": False} + {"brief": "test campaign", "buying_mode": "brief"} ) # Verify parsing was called with correct type mock_parse.assert_called_once_with(mock_raw_result, GetProductsResponse) @@ -380,7 +380,6 @@ async def test_get_products_wholesale_versions_sent_and_parsed(): mock_get.assert_called_once_with( { "buying_mode": "wholesale", - "include_collections": False, "pagination": {"max_results": 25}, "if_wholesale_feed_version": "wf_1", "if_pricing_version": "pr_1", @@ -976,8 +975,8 @@ async def test_multi_agent_parallel_execution(): results = await client.get_products(request) # Verify both agents' get_products method was called - mock1.assert_called_once_with({"buying_mode": "wholesale", "include_collections": False}) - mock2.assert_called_once_with({"buying_mode": "wholesale", "include_collections": False}) + mock1.assert_called_once_with({"buying_mode": "wholesale"}) + mock2.assert_called_once_with({"buying_mode": "wholesale"}) # Verify results from both agents assert len(results) == 2 @@ -1241,7 +1240,7 @@ async def test_get_media_buys_parses_response(): GetMediaBuysRequest.model_validate({"account": {"account_id": "acct-1"}}) ) mock_adapter.assert_called_once_with( - {"account": {"account_id": "acct-1"}, "include_history": 0, "include_snapshot": False} + {"account": {"account_id": "acct-1"}} ) assert result.success is True assert isinstance(result.data, GetMediaBuysResponse) @@ -1328,7 +1327,7 @@ async def test_get_media_buys_parses_snapshot_response(): ) ) mock_adapter.assert_called_once_with( - {"account": {"account_id": "acct-1"}, "include_history": 0, "include_snapshot": True} + {"account": {"account_id": "acct-1"}, "include_snapshot": True} ) assert result.success is True assert isinstance(result.data, GetMediaBuysResponse) diff --git a/tests/test_decisioning_capabilities_projection.py b/tests/test_decisioning_capabilities_projection.py index dbfafd841..ba70ee812 100644 --- a/tests/test_decisioning_capabilities_projection.py +++ b/tests/test_decisioning_capabilities_projection.py @@ -592,8 +592,8 @@ def get_adcp_capabilities_for_request(self, params=None, context=None): assert response["webhook_signing"] == { "supported": True, "profile": "adcp/webhook-signing/v1", - # 3.3 advertises the accepted profiles beside the legacy singular field. - "profiles": ["adcp/webhook-signing/v1"], + # ``profiles`` carries the 3.3 schema default and, unset, stays off the + # wire (SDK 10, #1613); a seller advertising it sets it explicitly. "algorithms": ["ed25519"], "legacy_hmac_fallback": True, "delivery_retry_horizon_seconds": 86400, diff --git a/tests/test_decisioning_negotiation.py b/tests/test_decisioning_negotiation.py index 66a839a24..2ca0041e3 100644 --- a/tests/test_decisioning_negotiation.py +++ b/tests/test_decisioning_negotiation.py @@ -640,11 +640,10 @@ def refine_proposals(self, req, ctx): legacy = await handler.get_adcp_capabilities({}, ToolContext(resolved_adcp_version="3.1")) assert "refine_proposals" in response["media_buy"]["lifecycle_tools"] - # hold_extension carries a schema default (false) since AdCP 3.3, so the - # projected refinement capability declares it. + # hold_extension carries a schema default (false) since AdCP 3.3; unset, + # it stays off the wire (SDK 10, #1613), and the reader applies the default. assert response["media_buy"]["proposal_refinement"] == { "supported_dimensions": ["total_budget"], - "hold_extension": False, } assert "refine_proposals" not in legacy["media_buy"].get("lifecycle_tools", []) assert "proposal_refinement" not in legacy["media_buy"] @@ -683,7 +682,6 @@ def refine_proposals(self, req, ctx): assert "refine_proposals" in response["media_buy"]["lifecycle_tools"] assert response["media_buy"]["proposal_refinement"] == { "supported_dimensions": ["cpm"], - "hold_extension": False, } diff --git a/tests/test_no_explicit_null.py b/tests/test_no_explicit_null.py index 6278c57f4..a734cbd86 100644 --- a/tests/test_no_explicit_null.py +++ b/tests/test_no_explicit_null.py @@ -265,19 +265,18 @@ def test_every_optional_field_either_carries_the_marker_or_admits_null( ) -def test_no_generated_module_still_carries_the_transient_mark() -> None: - """The ``x-adcp-no-explicit-null`` key is a generator-internal carrier and never ships.""" +@pytest.mark.parametrize("key", ["x-adcp-no-explicit-null", "x-adcp-omit-unset-default"]) +def test_no_generated_module_still_carries_the_transient_mark(key: str) -> None: + """A generator mark is a generator-internal carrier and never ships.""" domains = Path(public_types.__file__).parent / "domains" offenders = [ - path.relative_to(domains) - for path in domains.rglob("*.py") - if "x-adcp-no-explicit-null" in path.read_text() + path.relative_to(domains) for path in domains.rglob("*.py") if key in path.read_text() ] assert offenders == [] bundled = Path(public_types.__file__).parent.parent / "_schemas" if bundled.exists(): for path in bundled.rglob("*.json"): - assert "x-adcp-no-explicit-null" not in path.read_text(), path + assert key not in path.read_text(), path def test_marker_is_widespread_in_the_generated_tree() -> None: diff --git a/tests/test_post_generate_no_explicit_null.py b/tests/test_post_generate_no_explicit_null.py index e10c8d014..7277534fd 100644 --- a/tests/test_post_generate_no_explicit_null.py +++ b/tests/test_post_generate_no_explicit_null.py @@ -19,6 +19,10 @@ from scripts import post_generate_fixes as fixes MARK = fixes._NO_EXPLICIT_NULL_EXTRA +DEFAULT_MARK = "json_schema_extra={'x-adcp-omit-unset-default': True}" +BOTH_MARKS = ( + "json_schema_extra={'x-adcp-no-explicit-null': True, 'x-adcp-omit-unset-default': True}" +) def _rewrite(source: str, marker: str | None = fixes._NO_EXPLICIT_NULL_MARKER) -> tuple[str, int]: @@ -177,3 +181,78 @@ class X(AdCPBaseModel): "Ünïcode.'), NoExplicitNull" in rewritten or "Ünïcode.'),\n NoExplicitNull" in rewritten ) + + +def test_the_default_mark_becomes_omit_unset_default_whatever_the_annotation() -> None: + """A schema-optional default is marked even when the annotation refuses ``None``.""" + source = f"""\ + class X(AdCPBaseModel): + sandbox: Annotated[bool, Field(description='Sandbox flag.', {DEFAULT_MARK})] = False + tz: Annotated[Timezone | None, Field({BOTH_MARKS})] = 'local' + status: Annotated[Literal['closed'], Field({DEFAULT_MARK})] = 'closed' + """ + rewritten, count = _rewrite(source) + assert count == 3 + sandbox = "Annotated[bool, Field(description='Sandbox flag.'), OmitUnsetDefault] = False" + assert f" sandbox: {sandbox}\n" in rewritten + assert ( + " tz: Annotated[Timezone | None, NoExplicitNull, OmitUnsetDefault] = 'local'\n" + in rewritten + ) + assert " status: Annotated[Literal['closed'], OmitUnsetDefault] = 'closed'\n" in rewritten + + +def test_both_marks_suppressed_give_back_the_unmarked_rendering() -> None: + source = f"""\ + class X(AdCPBaseModel): + sandbox: Annotated[bool, Field({DEFAULT_MARK})] = False + tz: Annotated[Timezone | None, Field(description='kept', {BOTH_MARKS})] = 'local' + """ + rewritten, count = _rewrite(source, marker=None) + assert count == 0 + assert rewritten == textwrap.dedent("""\ + class X(AdCPBaseModel): + sandbox: bool = False + tz: Annotated[Timezone | None, Field(description='kept')] = 'local' + """) + + +def test_the_pass_imports_each_marker_it_wrote(tmp_path, monkeypatch) -> None: + generated_dir = tmp_path / "domains" + target = generated_dir / "core" / "thing.py" + target.parent.mkdir(parents=True) + target.write_text( + "from typing import Annotated\n\n" + "from adcp.types.base import AdCPBaseModel\n" + "from pydantic import Field\n\n\n" + "class X(AdCPBaseModel):\n" + f" sandbox: Annotated[bool, Field({DEFAULT_MARK})] = False\n" + f" ref: Annotated[str | None, Field({MARK})] = None\n" + ) + monkeypatch.setattr(fixes, "OUTPUT_DIR", generated_dir) + fixes.point_omittable_fields_at_no_explicit_null() + source = target.read_text() + assert "from adcp.types.base import AdCPBaseModel, NoExplicitNull, OmitUnsetDefault" in source + assert "x-adcp-" not in source + assert " sandbox: Annotated[bool, OmitUnsetDefault] = False\n" in source + assert " ref: Annotated[str | None, NoExplicitNull] = None\n" in source + + +def test_root_models_are_pointed_at_the_adcp_root(tmp_path, monkeypatch) -> None: + generated_dir = tmp_path / "domains" + target = generated_dir / "core" / "union.py" + target.parent.mkdir(parents=True) + target.write_text( + "from pydantic import ConfigDict, RootModel\n\n" + "from adcp.types.base import AdCPBaseModel\n\n\n" + "class A(AdCPBaseModel):\n pass\n\n\n" + "class U(RootModel[A | int]):\n root: A | int\n\n\n" + "class Wide(\n RootModel[\n A\n | int\n ]\n):\n pass\n" + ) + monkeypatch.setattr(fixes, "OUTPUT_DIR", generated_dir) + fixes.point_rootmodels_at_adcp_root() + source = target.read_text() + assert "class U(AdCPRootModel[A | int]):" in source + assert "class Wide(\n AdCPRootModel[" in source + assert "from adcp.types.base import AdCPBaseModel, AdCPRootModel" in source + assert "from pydantic import ConfigDict\n" in source diff --git a/tests/test_reporting_reconciliation.py b/tests/test_reporting_reconciliation.py index d5429ae9f..5ba6d472d 100644 --- a/tests/test_reporting_reconciliation.py +++ b/tests/test_reporting_reconciliation.py @@ -118,6 +118,10 @@ def test_capability_uses_public_reporting_delivery_model() -> None: "grain": "media_buy_day", "primary_keys": ["media_buy_id", "date"], "canonicalization_id": "rows-v1", + "canonicalization_contract_version": "1.0", + "canonicalization_media_type": ( + "application/vnd.adcp.reporting-canonicalization+json" + ), "canonicalization_uri": "https://schemas.example/canonicalization/rows-v1.json", "canonicalization_sha256": "b" * 64, }, diff --git a/tests/test_unset_defaults.py b/tests/test_unset_defaults.py new file mode 100644 index 000000000..e2a2c3326 --- /dev/null +++ b/tests/test_unset_defaults.py @@ -0,0 +1,213 @@ +"""The default dump leaves an unset schema default off (#1613). + +The generator writes a schema ``default`` into the field (``sandbox: bool = +False``), which is the right attribute surface. On the wire "omitted" and +"present with the default" are different bytes, and a digest or echo over the +document tells them apart. A schema-optional property with a default (or a +``const``) carries ``OmitUnsetDefault``, and the default dump emits it only +when it is in ``model_fields_set``. ``tests/conformance/test_wire_round_trip.py`` +holds every generated model to it with no ``default_emitted`` allowance. +""" + +from __future__ import annotations + +import copy +import importlib +import pkgutil +import typing +from typing import Annotated, Literal + +import pytest +from pydantic import Field, RootModel + +from adcp.types.base import ( + AdCPBaseModel, + AdCPRootModel, + NoExplicitNull, + OmitUnsetDefault, + inherit_field, + is_omit_unset_default, + omit_unset_default_fields, +) + + +class Model(AdCPBaseModel): + kind: Literal["thing"] = "thing" # required on the wire: no marker + sandbox: Annotated[bool, Field(description="Optional, defaults false."), OmitUnsetDefault] = ( + False + ) + timezone: Annotated[str | None, NoExplicitNull, OmitUnsetDefault] = "inventory_local" + field_schema: Annotated[str, Field(alias="$schema"), OmitUnsetDefault] = "draft-07" + plain: str | None = None + + +def test_marked_fields_are_the_schema_optional_defaults() -> None: + assert omit_unset_default_fields(Model) == {"sandbox", "timezone", "field_schema"} + assert is_omit_unset_default(Model.model_fields["sandbox"]) + assert not is_omit_unset_default(Model.model_fields["kind"]) + + +def test_the_marker_survives_a_copy() -> None: + assert copy.deepcopy(OmitUnsetDefault) is OmitUnsetDefault + assert copy.copy(OmitUnsetDefault) is OmitUnsetDefault + assert repr(OmitUnsetDefault) == "OmitUnsetDefault" + + +def test_a_parsed_document_dumps_without_the_defaults_it_omitted() -> None: + parsed = Model.model_validate_json("{}") + assert parsed.sandbox is False + assert parsed.timezone == "inventory_local" + assert parsed.model_dump(mode="json") == {"kind": "thing"} + assert parsed.model_dump_json() == '{"kind":"thing"}' + + +def test_a_default_the_document_carried_stays() -> None: + parsed = Model.model_validate_json('{"sandbox": false, "$schema": "draft-07"}') + assert parsed.model_dump(mode="json") == { + "kind": "thing", + "sandbox": False, + "$schema": "draft-07", + } + + +def test_a_python_built_model_dumps_what_the_caller_set_plus_the_wire_required() -> None: + assert Model().model_dump(mode="json") == {"kind": "thing"} + assert Model(sandbox=False).model_dump(mode="json") == {"kind": "thing", "sandbox": False} + model = Model() + model.timezone = "UTC" + assert model.model_dump(mode="json") == {"kind": "thing", "timezone": "UTC"} + + +def test_exclude_unset_false_restores_every_default() -> None: + parsed = Model.model_validate_json("{}") + assert parsed.model_dump(mode="json", exclude_unset=False) == { + "kind": "thing", + "sandbox": False, + "timezone": "inventory_local", + "$schema": "draft-07", + } + assert parsed.model_dump(mode="json", exclude_unset=True) == {} + + +def test_a_nested_model_follows_the_same_rule() -> None: + class Outer(AdCPBaseModel): + inner: Model + items: list[Model] = [] + + outer = Outer.model_validate({"inner": {}, "items": [{"sandbox": True}]}) + assert outer.model_dump(mode="json") == { + "inner": {"kind": "thing"}, + "items": [{"kind": "thing", "sandbox": True}], + } + assert outer.model_dump(mode="json", exclude_unset=False)["inner"]["sandbox"] is False + + +def test_by_alias_false_omits_by_python_name() -> None: + parsed = Model.model_validate_json("{}") + assert parsed.model_dump(mode="json", by_alias=False) == {"kind": "thing"} + + +def test_model_dump_as_received_is_a_deprecated_alias() -> None: + parsed = Model.model_validate_json('{"plain": null}') + with pytest.warns(DeprecationWarning, match="model_dump_as_received"): + received = parsed.model_dump_as_received() + assert received == parsed.model_dump(mode="json") == {"kind": "thing", "plain": None} + + +def test_inherit_field_required_drops_the_marker() -> None: + field = inherit_field(Model, "sandbox", required=True) + assert not any(item is OmitUnsetDefault for item in field.metadata) + kept = inherit_field(Model, "sandbox") + assert any(item is OmitUnsetDefault for item in kept.metadata) + + +def test_a_generated_root_model_dumps_like_its_arm() -> None: + class Arm(AdCPBaseModel): + x: int | None = None + sandbox: Annotated[bool, OmitUnsetDefault] = False + + class Root(AdCPRootModel[Arm | int]): + pass + + assert issubclass(Root, RootModel) + assert Root.model_validate({}).model_dump(mode="json") == {} + assert Root.model_validate({}).model_dump_json() == "{}" + assert Root.model_validate({}).model_dump(mode="json", exclude_unset=False) == { + "sandbox": False + } + assert Root.model_validate(3).model_dump() == 3 + + +# --- the generated tree --------------------------------------------------------------- + + +def _generated_models() -> list[type[AdCPBaseModel]]: + import adcp.types.domains as domains + + found: dict[type, None] = {} + for info in pkgutil.walk_packages(domains.__path__, prefix=domains.__name__ + "."): + try: + module = importlib.import_module(info.name) + except ImportError: + continue # a removed-schema shim that raises on import + for obj in list(vars(module).values()): + if ( + isinstance(obj, type) + and issubclass(obj, AdCPBaseModel) + and obj.__module__ == module.__name__ + ): + found[obj] = None + return list(found) + + +def test_the_marker_is_widespread_in_the_generated_tree() -> None: + """The rule is generator-level: hundreds of classes carry it.""" + marked = [model for model in _generated_models() if omit_unset_default_fields(model)] + assert len(marked) > 300, len(marked) + + +def test_no_generated_root_model_keeps_pydantic_s_dump() -> None: + """Every composing root the generator keeps wrapped is an ``AdCPRootModel``.""" + import adcp.types.domains as domains + + offenders = [] + for info in pkgutil.walk_packages(domains.__path__, prefix=domains.__name__ + "."): + try: + module = importlib.import_module(info.name) + except ImportError: + continue + for obj in list(vars(module).values()): + if ( + isinstance(obj, type) + and issubclass(obj, RootModel) + and obj.__module__ == module.__name__ + and obj is not RootModel + and not issubclass(obj, AdCPRootModel) + and "_ScalarRoot" not in {base.__name__ for base in obj.__mro__} + ): + offenders.append(f"{obj.__module__}.{obj.__name__}") + assert offenders == [], offenders + + +def test_a_generated_schema_default_round_trips() -> None: + from adcp.types.domains.core.account_ref import AccountReference2 + + assert omit_unset_default_fields(AccountReference2) == {"sandbox"} + document = {"brand": {"domain": "acme.example"}, "operator": "agency.example"} + parsed = AccountReference2.model_validate(document) + assert parsed.sandbox is False + assert parsed.model_dump(mode="json") == document + assert AccountReference2.model_validate({**document, "sandbox": False}).model_dump( + mode="json" + ) == {**document, "sandbox": False} + + +def test_a_required_discriminator_is_still_emitted_when_built_in_python() -> None: + """An injected single-value ``Literal`` default on a required property has no marker.""" + from adcp.types.domains.core.assets.image_asset import ImageAsset + + field = ImageAsset.model_fields["asset_type"] + assert typing.get_origin(field.annotation) is Literal + assert not is_omit_unset_default(field) + asset = ImageAsset(url="https://cdn.example/a.png", width=1, height=1) + assert asset.model_dump(mode="json")["asset_type"] == "image" diff --git a/tests/test_wire_types.py b/tests/test_wire_types.py index c613faacb..09afb7484 100644 --- a/tests/test_wire_types.py +++ b/tests/test_wire_types.py @@ -298,15 +298,21 @@ def test_model_dump_as_received_leaves_off_what_the_document_omitted() -> None: "terms_digest": "sha256:" + base64.urlsafe_b64encode(digest).rstrip(b"=").decode(), } parsed = CanonicalProposal.model_validate_json(json.dumps(proposal)) - # The default dump materializes ``verification_required: false`` (#1613)... - overlay = parsed.model_dump(mode="json")["commercial_terms"]["purchases"][0][ - "targeting_overlay" - ] - assert overlay["age_restriction"] == {"min": 21, "verification_required": False} - # ...and the as-received dump is the document, so terms_digest verifies. - received = parsed.model_dump_as_received() + # The default dump is the document (SDK 10, #1613): the schema default + # ``verification_required: false`` the seller left out stays out, so + # terms_digest verifies over the default dump... + dumped = parsed.model_dump(mode="json") + assert dumped == proposal + assert rfc8785.dumps(dumped["commercial_terms"]) == rfc8785.dumps(terms) + overlay = dumped["commercial_terms"]["purchases"][0]["targeting_overlay"] + assert overlay["age_restriction"] == {"min": 21} + # ...the attribute still reads the default... + purchase = parsed.commercial_terms.purchases[0] + assert purchase.targeting_overlay.age_restriction.verification_required is False + # ...and the deprecated as-received dump says the same. + with pytest.warns(DeprecationWarning): + received = parsed.model_dump_as_received() assert received == proposal - assert rfc8785.dumps(received["commercial_terms"]) == rfc8785.dumps(terms) def test_model_dump_as_received_keeps_an_admitted_null_and_the_wire_alias() -> None: @@ -316,7 +322,8 @@ def test_model_dump_as_received_keeps_an_admitted_null_and_the_wire_alias() -> N parsed = TargetingInput.model_validate_json(json.dumps(document)) # A received null is re-emitted by the default dump too (SDK 10, #1615). assert parsed.model_dump(mode="json") == document - assert parsed.model_dump_as_received() == document + with pytest.warns(DeprecationWarning): + assert parsed.model_dump_as_received() == document # ---------------------------------------------------------------------------