diff --git a/scripts/post_generate_fixes.py b/scripts/post_generate_fixes.py index a8576a0f..dfee54e2 100644 --- a/scripts/post_generate_fixes.py +++ b/scripts/post_generate_fixes.py @@ -21,7 +21,9 @@ import importlib.util import json import re +from copy import deepcopy from pathlib import Path +from typing import Any REPO_ROOT = Path(__file__).parent.parent @@ -1484,15 +1486,15 @@ def _ensure_sequence_import(content: str) -> str: new_line = f"from collections.abc import {', '.join(names)}" return content[: match.start()] + new_line + content[match.end() :] - # Otherwise insert after the typing imports block. Codegen always emits - # ``from typing import Annotated`` near the top, so anchor on it. + # Otherwise insert before the typing imports block. Codegen emits a + # ``from typing import ...`` line near the top, so anchor on it. typing_pattern = re.compile(r"^from typing import [^\n]+$", re.MULTILINE) match = typing_pattern.search(content) if match is not None: return ( - content[: match.end()] - + "\nfrom collections.abc import Sequence" - + content[match.end() :] + content[: match.start()] + + "from collections.abc import Sequence\n" + + content[match.start() :] ) # Fallback: prepend after the `from __future__` line. @@ -1977,706 +1979,559 @@ def restore_format_asset_numbered_aliases() -> None: def restore_response_variant_aliases() -> None: - """Restore public numbered response arms for beta 3 envelope-only responses. + """Restore numbered response arms from schema data, not hand-written payloads. - Beta 3 moved many response schemas to a common protocol envelope shape. - The SDK has long exposed numbered success/error/submitted arm classes, and - helpers/tests/adopters construct those classes directly. Reintroduce thin, - extra-allowing compatibility arms and make the public response name a union - when the generator no longer emits arms. + datamodel-code-generator currently collapses several task responses to a + common envelope class with no task-specific fields. The SDK still exposes + numbered response-arm classes for ergonomic construction/parsing aliases. + Keep those public names, but derive their fields from the JSON schemas so + the compatibility layer cannot silently drift from protocol shape. """ - common_header = """ - - -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias - -from pydantic import ConfigDict - -from ..core import error as error_1 -""" - - media_header = """ - - -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias - -from pydantic import AwareDatetime, ConfigDict, model_validator - -from adcp.types.media_buy_status_helpers import MEDIA_BUY_LEGACY_STATUS_VALUES, unwrap_enum_value - -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core import package as package_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..enums import media_buy_status as media_buy_status_1 -from ..enums import task_status as task_status_1 -""" - update_media_header = media_header.replace( - "from typing import Any, Literal, TypeAlias", - "from collections.abc import Sequence\nfrom typing import Any, Literal, TypeAlias", - ) - - simple_error_arms: dict[str, tuple[str, str, str]] = { - "media_buy/build_creative_response.py": ( - "BuildCreativeResponse", - "BuildCreativeResponse1", - "BuildCreativeResponse2", - ), - "media_buy/provide_performance_feedback_response.py": ( - "ProvidePerformanceFeedbackResponse", - "ProvidePerformanceFeedbackResponse1", - "ProvidePerformanceFeedbackResponse2", - ), - "account/sync_accounts_response.py": ( - "SyncAccountsResponse", - "SyncAccountsResponse1", - "SyncAccountsResponse2", - ), - "media_buy/log_event_response.py": ( - "LogEventResponse", - "LogEventResponse1", - "LogEventResponse2", - ), - "media_buy/sync_event_sources_response.py": ( - "SyncEventSourcesResponse", - "SyncEventSourcesResponse1", - "SyncEventSourcesResponse2", - ), - "media_buy/sync_audiences_response.py": ( - "SyncAudiencesResponse", - "SyncAudiencesResponse1", - "SyncAudiencesResponse2", - ), - "account/get_account_financials_response.py": ( - "GetAccountFinancialsResponse", - "GetAccountFinancialsResponse1", - "GetAccountFinancialsResponse2", - ), - "content_standards/calibrate_content_response.py": ( - "CalibrateContentResponse", - "CalibrateContentResponse1", - "CalibrateContentResponse2", - ), - "content_standards/validate_content_delivery_response.py": ( + response_specs: tuple[tuple[str, str], ...] = ( + ("account/get_account_financials_response.py", "GetAccountFinancialsResponse"), + ("account/sync_accounts_response.py", "SyncAccountsResponse"), + ("brand/acquire_rights_response.py", "AcquireRightsResponse"), + ("brand/get_brand_identity_response.py", "GetBrandIdentityResponse"), + ("brand/get_rights_response.py", "GetRightsResponse"), + ("brand/update_rights_response.py", "UpdateRightsResponse"), + ("content_standards/calibrate_content_response.py", "CalibrateContentResponse"), + ("content_standards/get_content_standards_response.py", "GetContentStandardsResponse"), + ("content_standards/get_media_buy_artifacts_response.py", "GetMediaBuyArtifactsResponse"), + ( + "content_standards/validate_content_delivery_response.py", "ValidateContentDeliveryResponse", - "ValidateContentDeliveryResponse1", - "ValidateContentDeliveryResponse2", ), - "brand/get_rights_response.py": ( - "GetRightsResponse", - "GetRightsResponse1", - "GetRightsResponse2", + ("creative/get_creative_features_response.py", "GetCreativeFeaturesResponse"), + ("creative/preview_creative_response.py", "PreviewCreativeResponse"), + ("creative/sync_creatives_response.py", "SyncCreativesResponse"), + ("media_buy/build_creative_response.py", "BuildCreativeResponse"), + ("media_buy/create_media_buy_response.py", "CreateMediaBuyResponse"), + ("media_buy/log_event_response.py", "LogEventResponse"), + ( + "media_buy/provide_performance_feedback_response.py", + "ProvidePerformanceFeedbackResponse", ), - } + ("media_buy/sync_audiences_response.py", "SyncAudiencesResponse"), + ("media_buy/sync_catalogs_response.py", "SyncCatalogsResponse"), + ("media_buy/sync_event_sources_response.py", "SyncEventSourcesResponse"), + ("media_buy/update_media_buy_response.py", "UpdateMediaBuyResponse"), + ("signals/activate_signal_response.py", "ActivateSignalResponse"), + ) fixed = 0 - def _remove_original_response_class(source: str, base: str) -> str: - """Remove the generator's envelope-only class before restoring a union alias.""" - source = re.sub( - rf"\n\nclass {re.escape(base)}\(AdcpVersionEnvelope, ProtocolEnvelope\):\n pass\n", - "\n", - source, - ) - return _sync_protocol_envelope_import(source) - - def _normalize_existing_arms(target: Path, base: str) -> None: - """Keep compatibility arms payload-shaped and expose final names as aliases.""" - original = target.read_text() - source = _remove_original_response_class(original, base) - new_source = re.sub( - r"class ([A-Za-z]+Response[12])\(AdcpVersionEnvelope, ProtocolEnvelope\):", - r"class \1(AdcpVersionEnvelope):", - source, - ) - new_source = new_source.replace( - "from typing import Any, Literal\n", - "from typing import Any, Literal, TypeAlias\n", - ) - new_source = re.sub( - rf"\n{re.escape(base)} = ", - f"\n{base}: TypeAlias = ", - new_source, + def _schema_relative(relative: str) -> Path: + rel = Path(relative).with_suffix(".json") + return Path(*(part.replace("_", "-") for part in rel.parts)) + + def _pascal(value: str) -> str: + words = re.split(r"[^A-Za-z0-9]+", value) + return "".join(word[:1].upper() + word[1:] for word in words if word) + + def _singular_pascal(value: str) -> str: + if value.endswith("ies"): + value = value[:-3] + "y" + elif value.endswith("ses"): + value = value[:-2] + elif value.endswith("s") and not value.endswith("ss"): + value = value[:-1] + return _pascal(value) + + def _load_schema(schema_rel: Path) -> dict[str, Any]: + return json.loads((SCHEMA_DIR / schema_rel).read_text()) + + def _schema_title(schema_rel: Path) -> str: + schema = _load_schema(schema_rel) + title = schema.get("title") + if isinstance(title, str) and title.strip(): + return title + return schema_rel.stem + + def _generated_module_path(schema_rel: Path) -> Path: + parts = [part.replace("-", "_") for part in schema_rel.with_suffix(".py").parts] + return OUTPUT_DIR / Path(*parts) + + def _generated_class_name(schema_rel: Path) -> str: + fallback = _pascal(_schema_title(schema_rel)) + module_path = _generated_module_path(schema_rel) + if not module_path.exists(): + return fallback + try: + tree = ast.parse(module_path.read_text()) + except SyntaxError: + return fallback + class_names = [node.name for node in tree.body if isinstance(node, ast.ClassDef)] + if not class_names: + return fallback + normalized_fallback = fallback.lower() + for name in class_names: + if name.lower() == normalized_fallback: + return name + stem_fallback = _pascal(schema_rel.stem).lower() + for name in class_names: + if name.lower() == stem_fallback: + return name + return class_names[-1] + + def _resolve_ref(schema_rel: Path, ref: str) -> Path: + file_ref = ref.split("#", 1)[0] + if file_ref.startswith("/schemas/"): + return Path("/".join(file_ref.split("/")[3:])) + return ( + (SCHEMA_DIR / schema_rel.parent / file_ref).resolve().relative_to(SCHEMA_DIR.resolve()) ) - new_source = _sync_protocol_envelope_import(new_source) - if new_source != original: - target.write_text(new_source) - def _write_if_needed(relative: str, base: str, marker: str, snippet: str) -> None: - nonlocal fixed + def _safe_import_alias(module_stem: str, used: set[str]) -> str: + base = module_stem.replace("-", "_") + alias = f"{base}_1" + index = 1 + while alias in used: + index += 1 + alias = f"{base}_{index}" + used.add(alias) + return alias + + def _json_pointer_get(schema: dict[str, Any], pointer: str) -> Any: + if not pointer.startswith("#/"): + raise KeyError(pointer) + node: Any = schema + for raw_part in pointer[2:].split("/"): + part = raw_part.replace("~1", "/").replace("~0", "~") + if isinstance(node, list): + node = node[int(part)] + elif isinstance(node, dict): + node = node[part] + else: + raise KeyError(pointer) + return node + + def _merge_all_of(parts: list[Any]) -> dict[str, Any] | None: + merged: dict[str, Any] = {"type": "object", "properties": {}, "required": []} + for part in parts: + if not isinstance(part, dict): + return None + if "$ref" in part or "oneOf" in part or "anyOf" in part: + return None + if part.get("type") not in {None, "object"} and "properties" not in part: + return None + merged["properties"].update(deepcopy(part.get("properties") or {})) + merged["required"].extend(part.get("required") or []) + if "additionalProperties" in part: + merged["additionalProperties"] = part["additionalProperties"] + merged["required"] = sorted(set(merged["required"])) + return merged + + class Emitter: + def __init__(self, relative: str, base: str, schema_rel: Path): + self.relative = relative + self.base = base + self.schema_rel = schema_rel + self.imports: dict[str, set[str]] = {} + self.pydantic_imports: set[str] = {"ConfigDict"} + self.typing_imports: set[str] = {"TypeAlias"} + self.used_aliases: set[str] = set() + self.import_aliases: dict[str, str] = {} + self.nested: list[str] = [] + self.nested_names: set[str] = set() + self.local_ref_types: dict[str, str] = {} + self.root_schema: dict[str, Any] = {} + self.needs_protocol_envelope = False + self.needs_media_buy_helpers = False + self.needs_sequence = False + self.datetime_imports: set[str] = set() + + def add_import(self, line: str) -> None: + self.imports.setdefault(line, set()) + + def import_alias(self, import_line: str, module_stem: str) -> str: + if import_line in self.import_aliases: + return self.import_aliases[import_line] + alias = _safe_import_alias(module_stem, self.used_aliases) + self.import_aliases[import_line] = alias + self.add_import(import_line.format(alias=alias)) + return alias + + def ref_type(self, schema: dict[str, Any]) -> str: + ref = schema.get("$ref") + if not isinstance(ref, str): + return "Any" + if ref.startswith("#"): + if ref in self.local_ref_types: + return self.local_ref_types[ref] + try: + ref_schema = _json_pointer_get(self.root_schema, ref) + except (KeyError, IndexError, ValueError, TypeError): + self.typing_imports.add("Any") + return "Any" + name = _pascal(ref.rsplit("/", 1)[-1]) + typ = self.type_for(name, ref_schema if isinstance(ref_schema, dict) else {}) + self.local_ref_types[ref] = typ + return typ + ref_rel = _resolve_ref(self.schema_rel, ref) + parts = list(ref_rel.parts) + module_stem = ref_rel.stem.replace("-", "_") + class_name = _generated_class_name(ref_rel) + if parts[0] == "enums": + alias = self.import_alias( + f"from ..enums import {module_stem} as {{alias}}", module_stem + ) + return f"{alias}.{class_name}" + if parts[0] == "core": + alias = self.import_alias( + f"from ..core import {module_stem} as {{alias}}", module_stem + ) + return f"{alias}.{class_name}" + if parts[0] == Path(self.relative).parts[0].replace("_", "-"): + alias = self.import_alias(f"from . import {module_stem} as {{alias}}", module_stem) + return f"{alias}.{class_name}" + module_dir = parts[0].replace("-", "_") + alias = self.import_alias( + f"from ..{module_dir} import {module_stem} as {{alias}}", module_stem + ) + return f"{alias}.{class_name}" + + def string_type(self, schema: dict[str, Any]) -> str: + constraints: list[str] = [] + if isinstance(schema.get("pattern"), str): + constraints.append(f"pattern={schema['pattern']!r}") + if isinstance(schema.get("minLength"), int): + constraints.append(f"min_length={schema['minLength']}") + if isinstance(schema.get("maxLength"), int): + constraints.append(f"max_length={schema['maxLength']}") + if constraints: + self.typing_imports.add("Annotated") + self.pydantic_imports.add("StringConstraints") + return f"Annotated[str, StringConstraints({', '.join(constraints)})]" + if schema.get("format") == "uri": + self.pydantic_imports.add("AnyUrl") + return "AnyUrl" + if schema.get("format") == "date-time": + self.pydantic_imports.add("AwareDatetime") + return "AwareDatetime" + if schema.get("format") == "date": + self.datetime_imports.add("date") + return "date" + return "str" + + def constrained_number_type(self, base: str, schema: dict[str, Any]) -> str: + constraints: list[str] = [] + if "minimum" in schema: + constraints.append(f"ge={schema['minimum']!r}") + if "maximum" in schema: + constraints.append(f"le={schema['maximum']!r}") + if "exclusiveMinimum" in schema: + constraints.append(f"gt={schema['exclusiveMinimum']!r}") + if "exclusiveMaximum" in schema: + constraints.append(f"lt={schema['exclusiveMaximum']!r}") + if not constraints: + return base + self.typing_imports.add("Annotated") + self.pydantic_imports.add("Field") + return f"Annotated[{base}, Field({', '.join(constraints)})]" + + def maybe_constrain_array(self, typ: str, schema: dict[str, Any]) -> str: + constraints: list[str] = [] + if isinstance(schema.get("minItems"), int): + constraints.append(f"min_length={schema['minItems']}") + if isinstance(schema.get("maxItems"), int): + constraints.append(f"max_length={schema['maxItems']}") + if not constraints: + return typ + self.typing_imports.add("Annotated") + self.pydantic_imports.add("Field") + return f"Annotated[{typ}, Field({', '.join(constraints)})]" + + def type_for(self, name: str, schema: dict[str, Any]) -> str: + if "$ref" in schema: + return self.ref_type(schema) + if "const" in schema: + self.typing_imports.add("Literal") + return f"Literal[{schema['const']!r}]" + enum = schema.get("enum") + if isinstance(enum, list) and enum: + self.typing_imports.add("Literal") + values = ", ".join(repr(v) for v in enum) + return f"Literal[{values}]" + if "oneOf" in schema or "anyOf" in schema: + variants = schema.get("oneOf") or schema.get("anyOf") or [] + types = [] + for index, variant in enumerate(variants, 1): + if isinstance(variant, dict): + if variant.get("type") == "null": + types.append("None") + else: + types.append(self.type_for(f"{name}{index}", variant)) + unique_types = list(dict.fromkeys(types)) + if unique_types: + return " | ".join(unique_types) + self.typing_imports.add("Any") + return "Any" + if "allOf" in schema: + merged = _merge_all_of(schema.get("allOf") or []) + if merged is not None: + return self.emit_nested(_pascal(name), merged) + self.typing_imports.add("Any") + return "Any" + schema_type = schema.get("type") + if isinstance(schema_type, list): + non_null = [item for item in schema_type if item != "null"] + if len(non_null) == 1: + schema_type = non_null[0] + else: + self.typing_imports.add("Any") + return "Any" + if schema_type == "string": + return self.string_type(schema) + if schema_type == "integer": + return self.constrained_number_type("int", schema) + if schema_type == "number": + return self.constrained_number_type("float", schema) + if schema_type == "boolean": + return "bool" + if schema_type == "array": + item_schema = schema.get("items") + if isinstance(item_schema, dict): + if item_schema.get("type") == "object" and isinstance( + item_schema.get("properties"), dict + ): + class_name = self.emit_nested(_singular_pascal(name), item_schema) + return self.maybe_constrain_array(f"list[{class_name}]", schema) + return self.maybe_constrain_array( + f"list[{self.type_for(_singular_pascal(name), item_schema)}]", + schema, + ) + self.typing_imports.add("Any") + return self.maybe_constrain_array("list[Any]", schema) + if ( + schema_type == "object" + or "properties" in schema + or "additionalProperties" in schema + ): + if isinstance(schema.get("properties"), dict) and schema["properties"]: + return self.emit_nested(_pascal(name), schema) + pattern_props = schema.get("patternProperties") + if isinstance(pattern_props, dict) and pattern_props: + pattern, value_schema = next(iter(pattern_props.items())) + key_type = "str" + if pattern: + self.typing_imports.add("Annotated") + self.pydantic_imports.add("StringConstraints") + key_type = f"Annotated[str, StringConstraints(pattern={pattern!r})]" + value_type = "Any" + if isinstance(value_schema, dict): + value_type = self.type_for(f"{name}_value", value_schema) + return f"dict[{key_type}, {value_type}]" + additional = schema.get("additionalProperties") + if isinstance(additional, dict): + return f"dict[str, {self.type_for(f'{name}_value', additional)}]" + self.typing_imports.add("Any") + return "dict[str, Any]" + self.typing_imports.add("Any") + return "Any" + + def emit_nested(self, preferred: str, schema: dict[str, Any]) -> str: + class_name = preferred + suffix = 1 + while class_name in self.nested_names: + suffix += 1 + class_name = f"{preferred}{suffix}" + self.nested_names.add(class_name) + required = set(schema.get("required") or []) + lines = [ + f"class {class_name}(AdcpVersionEnvelope):", + " model_config = ConfigDict(extra='allow')", + ] + props = schema.get("properties") or {} + if not props: + lines.append(" pass") + for prop_name, prop_schema in props.items(): + if not isinstance(prop_schema, dict): + self.typing_imports.add("Any") + typ = "Any" + else: + typ = self.type_for(prop_name, prop_schema) + if prop_name in required: + lines.append(f" {prop_name}: {typ}") + else: + lines.append(f" {prop_name}: {typ} | None = None") + self.nested.append("\n".join(lines)) + return class_name + + def emit_response_class(self, class_name: str, arm: dict[str, Any]) -> str: + props = arm.get("properties") or {} + required = set(arm.get("required") or []) + is_submitted = ( + props.get("status", {}).get("const") == "submitted" and "task_id" in props + ) + bases = ( + "AdcpVersionEnvelope, ProtocolEnvelope" if is_submitted else "AdcpVersionEnvelope" + ) + if is_submitted: + self.needs_protocol_envelope = True + lines = [f"class {class_name}({bases}):"] + if is_submitted: + lines.append(" model_config = ConfigDict(extra='allow', validate_default=True)") + else: + lines.append(" model_config = ConfigDict(extra='allow')") + for prop_name, prop_schema in props.items(): + if is_submitted and prop_name == "status": + alias = self.import_alias( + "from ..enums import task_status as {alias}", "task_status" + ) + self.typing_imports.add("Literal") + lines.append( + f" status: Literal[{alias}.TaskStatus.submitted] = " + f"{alias}.TaskStatus.submitted" + ) + continue + if ( + self.base in {"CreateMediaBuyResponse", "UpdateMediaBuyResponse"} + and class_name.endswith("1") + and prop_name == "status" + ): + self.typing_imports.add("Literal") + lines.append(" status: Literal['completed']") + continue + if not isinstance(prop_schema, dict): + self.typing_imports.add("Any") + typ = "Any" + else: + typ = self.type_for(prop_name, prop_schema) + if ( + self.base == "UpdateMediaBuyResponse" + and class_name.endswith("1") + and prop_name == "affected_packages" + and typ.startswith("list[") + ): + self.needs_sequence = True + typ = f"Sequence[{typ.removeprefix('list[')}" + if prop_name in required: + const = prop_schema.get("const") + if isinstance(const, str): + lines.append(f" {prop_name}: {typ} = {const!r}") + else: + lines.append(f" {prop_name}: {typ}") + else: + lines.append(f" {prop_name}: {typ} | None = None") + if self.base in { + "CreateMediaBuyResponse", + "UpdateMediaBuyResponse", + } and class_name.endswith("1"): + self.needs_media_buy_helpers = True + lines.append("") + lines.append(" @model_validator(mode='before')") + lines.append(" @classmethod") + lines.append(" def _normalize_legacy_status(cls, data: Any) -> Any:") + lines.append(" if not isinstance(data, dict):") + lines.append(" return data") + lines.append(" raw_status = unwrap_enum_value(data.get('status'))") + lines.append( + " media_buy_status = unwrap_enum_value(data.get('media_buy_status'))" + ) + lines.append(" if raw_status is None:") + lines.append(" data = dict(data)") + lines.append(" data['status'] = 'completed'") + lines.append(" elif raw_status == 'completed':") + lines.append(" data = dict(data)") + lines.append(" data['status'] = 'completed'") + lines.append( + " elif media_buy_status is None and raw_status in MEDIA_BUY_LEGACY_STATUS_VALUES:" + ) + lines.append(" data = dict(data)") + lines.append(" data['media_buy_status'] = raw_status") + lines.append(" data['status'] = 'completed'") + lines.append( + " elif media_buy_status is not None and raw_status == media_buy_status:" + ) + lines.append(" data = dict(data)") + lines.append(" data['status'] = 'completed'") + lines.append(" return data") + return "\n".join(lines) + + def render(self, schema: dict[str, Any]) -> str: + self.root_schema = schema + arms = schema.get("oneOf") or schema.get("anyOf") or [] + if not arms: + arms = [schema] + if self.base == "BuildCreativeResponse" and len(arms) == 6: + # Preserve the long-standing public numbering where + # Response1 is the simple success arm and Response2 is the + # error arm; append newer schema branches after those. + arms = [arms[index] for index in (0, 4, 1, 2, 3, 5)] + class_names = [f"{self.base}{index}" for index in range(1, len(arms) + 1)] + response_classes = [ + self.emit_response_class(name, arm) for name, arm in zip(class_names, arms) + ] + if self.needs_protocol_envelope: + self.add_import("from ..core.protocol_envelope import ProtocolEnvelope") + if self.needs_media_buy_helpers: + self.add_import( + "from adcp.types.media_buy_status_helpers import " + "MEDIA_BUY_LEGACY_STATUS_VALUES, unwrap_enum_value" + ) + self.pydantic_imports.add("model_validator") + self.typing_imports.add("Any") + if any(re.search(r"\bAny\b", block) for block in self.nested + response_classes): + self.typing_imports.add("Any") + header = [ + "# generated by datamodel-codegen:", + f"# filename: {self.relative.replace('.py', '.json')}", + "# timestamp: preserved-by-post-generate-fixes", + "", + "from __future__ import annotations", + "", + ] + if self.datetime_imports: + header.extend( + [f"from datetime import {', '.join(sorted(self.datetime_imports))}", ""] + ) + if self.needs_sequence: + header.extend(["from collections.abc import Sequence", ""]) + header.extend( + [ + f"from typing import {', '.join(sorted(self.typing_imports))}", + "", + f"from pydantic import {', '.join(sorted(self.pydantic_imports))}", + "", + "from ..core.version_envelope import AdcpVersionEnvelope", + ] + ) + for import_line in sorted(self.imports): + header.append(import_line) + body = [] + body.extend(self.nested) + body.extend(response_classes) + union = " | ".join(class_names) + body.append(f"{self.base}: TypeAlias = {union}") + all_names = [self.base, *class_names, *sorted(self.nested_names)] + all_block = ["__all__ = ["] + for name in all_names: + all_block.append(f" {name!r},") + all_block.append("]") + body.append("\n".join(all_block)) + return "\n".join(header) + "\n\n\n" + "\n\n\n".join(body) + "\n" + + for relative, base in response_specs: target = OUTPUT_DIR / relative if not target.exists(): print(f" {relative} not found (skipping response arms)") - return - source = target.read_text() - if marker in source: - _normalize_existing_arms(target, base) - print(f" {relative}: response arms already restored") - return - source = _remove_original_response_class(source, base) - target.write_text(source.rstrip() + snippet.rstrip() + "\n") - _normalize_existing_arms(target, base) - fixed += 1 - - _write_if_needed( - "signals/activate_signal_response.py", - "ActivateSignalResponse", - "class ActivateSignalResponse1", - """ - - -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Literal, TypeAlias - -from pydantic import ConfigDict - -from ..core import deployment as deployment_1 -from ..core import error as error_1 - - -class ActivateSignalResponse1(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - deployments: list[deployment_1.Deployment] - sandbox: bool | None = None - - -class ActivateSignalResponse2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] - - -ActivateSignalResponse: TypeAlias = ActivateSignalResponse1 | ActivateSignalResponse2 -""", - ) - - restored_payload_arms: list[tuple[str, str, str, str]] = [ - ( - "brand/acquire_rights_response.py", - "AcquireRightsResponse", - "class AcquireRightsResponse1", - """ - - -from typing import Any, Literal, TypeAlias - -from pydantic import ConfigDict - -from ..core import error as error_1 - - -class AcquireRightsResponse1(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - rights_id: str - brand_id: str - terms: Any - generation_credentials: list[Any] - rights_constraint: Any - rights_status: Literal['acquired'] | None = None - status: Literal['acquired'] | None = None - - -class AcquireRightsResponse2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - rights_id: str - brand_id: str - rights_status: Literal['pending_approval'] | None = None - status: Literal['pending_approval'] | None = None - detail: str | None = None - estimated_response_time: str | None = None - - -class AcquireRightsResponse3(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - rights_id: str - brand_id: str - reason: str - rights_status: Literal['rejected'] | None = None - status: Literal['rejected'] | None = None - suggestions: list[str] | None = None - - -class AcquireRightsResponse4(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] - - -AcquireRightsResponse: TypeAlias = ( - AcquireRightsResponse1 - | AcquireRightsResponse2 - | AcquireRightsResponse3 - | AcquireRightsResponse4 -) -""", - ), - ( - "content_standards/get_content_standards_response.py", - "GetContentStandardsResponse", - "class GetContentStandardsResponse1", - """ - - -from typing import TypeAlias - -from pydantic import ConfigDict - -from ..core import error as error_1 - - -class GetContentStandardsResponse1(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - - -class GetContentStandardsResponse2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] - - -GetContentStandardsResponse: TypeAlias = ( - GetContentStandardsResponse1 | GetContentStandardsResponse2 -) -""", - ), - ( - "brand/get_brand_identity_response.py", - "GetBrandIdentityResponse", - "class GetBrandIdentityResponse1", - """ - - -from typing import TypeAlias - -from pydantic import ConfigDict - -from ..core import error as error_1 - - -class GetBrandIdentityResponse1(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - brand_id: str - house: str - names: dict[str, Any] - description: str | None = None - tagline: str | None = None - industries: list[str] | None = None - tone: Any = None - visual_guidelines: Any = None - colors: Any = None - logos: Any = None - fonts: Any = None - assets: Any = None - rights: Any = None - voice_synthesis: Any = None - keller_type: Any = None - available_fields: list[str] | None = None - - -class GetBrandIdentityResponse2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] - - -GetBrandIdentityResponse: TypeAlias = GetBrandIdentityResponse1 | GetBrandIdentityResponse2 -""", - ), - ( - "creative/get_creative_features_response.py", - "GetCreativeFeaturesResponse", - "class GetCreativeFeaturesResponse1", - """ - - -from typing import TypeAlias - -from pydantic import ConfigDict - -from ..core import creative_consumption as creative_consumption_1 -from ..core import error as error_1 -from . import creative_feature_result as creative_feature_result_1 - - -class GetCreativeFeaturesResponse1(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - results: list[creative_feature_result_1.CreativeFeatureResult] - detail_url: str | None = None - pricing_option_id: str | None = None - vendor_cost: float | None = None - currency: str | None = None - consumption: creative_consumption_1.CreativeConsumption | None = None - - -class GetCreativeFeaturesResponse2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] - - -GetCreativeFeaturesResponse: TypeAlias = ( - GetCreativeFeaturesResponse1 | GetCreativeFeaturesResponse2 -) -""", - ), - ( - "content_standards/get_media_buy_artifacts_response.py", - "GetMediaBuyArtifactsResponse", - "class GetMediaBuyArtifactsResponse1", - """ - - -from typing import Any, TypeAlias - -from pydantic import ConfigDict - -from ..core import error as error_1 -from . import artifact as artifact_1 - - -class ArtifactRecord(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - record_id: str - artifact: artifact_1.Artifact - timestamp: str | None = None - package_id: str | None = None - country: str | None = None - channel: str | None = None - brand_context: dict[str, Any] | None = None - local_verdict: str | None = None - - -class GetMediaBuyArtifactsResponse1(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - media_buy_id: str - artifacts: list[ArtifactRecord] - collection_info: dict[str, Any] | None = None - pagination: Any = None - - -class GetMediaBuyArtifactsResponse2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] - - -GetMediaBuyArtifactsResponse: TypeAlias = ( - GetMediaBuyArtifactsResponse1 | GetMediaBuyArtifactsResponse2 -) -""", - ), - ] - - for relative, base, marker, snippet in restored_payload_arms: - _write_if_needed(relative, base, marker, snippet) - - for relative, (base, success, error) in simple_error_arms.items(): - snippet = ( - common_header - + f""" - -class {success}(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - status: Any = None - - -class {error}(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] - - -{base}: TypeAlias = {success} | {error} -""" - ) - _write_if_needed(relative, base, f"class {success}", snippet) - - _write_if_needed( - "media_buy/create_media_buy_response.py", - "CreateMediaBuyResponse", - "class CreateMediaBuyResponse1", - media_header - + """ -class CreateMediaBuyResponse1(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - media_buy_id: str - packages: list[package_1.Package] - buyer_ref: str | None = None - confirmed_at: AwareDatetime | None - revision: int - media_buy_status: media_buy_status_1.MediaBuyStatus | None = None - status: Literal["completed"] - - @model_validator(mode='before') - @classmethod - def _normalize_legacy_status(cls, data: Any) -> Any: - if not isinstance(data, dict): - return data - raw_status = unwrap_enum_value(data.get("status")) - media_buy_status = unwrap_enum_value(data.get("media_buy_status")) - if raw_status is None: - data = dict(data) - data["status"] = "completed" - elif raw_status == "completed": - data = dict(data) - data["status"] = "completed" - elif media_buy_status is None and raw_status in MEDIA_BUY_LEGACY_STATUS_VALUES: - data = dict(data) - data["media_buy_status"] = raw_status - data["status"] = "completed" - elif media_buy_status is not None and raw_status == media_buy_status: - data = dict(data) - data["status"] = "completed" - return data - - -class CreateMediaBuyResponse2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] - - -class CreateMediaBuyResponse3(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow', use_enum_values=True, validate_default=True) - status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted - task_id: str - errors: list[error_1.Error] | None = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='before') - @classmethod - def _normalize_submitted_status(cls, data: Any) -> Any: - if isinstance(data, dict) and data.get("status") == "submitted": - data = dict(data) - data["status"] = task_status_1.TaskStatus.submitted - return data - - -CreateMediaBuyResponse: TypeAlias = ( - CreateMediaBuyResponse1 | CreateMediaBuyResponse2 | CreateMediaBuyResponse3 -) - -__all__ = [ - "CreateMediaBuyResponse", - "CreateMediaBuyResponse1", - "CreateMediaBuyResponse2", - "CreateMediaBuyResponse3", -] -""", - ) - - _write_if_needed( - "media_buy/update_media_buy_response.py", - "UpdateMediaBuyResponse", - "class UpdateMediaBuyResponse1", - update_media_header - + """ - -class UpdateMediaBuyResponse1(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - media_buy_id: str - affected_packages: Sequence[package_1.Package] | None = None - packages: list[package_1.Package] | None = None - buyer_ref: str | None = None - revision: int - media_buy_status: media_buy_status_1.MediaBuyStatus | None = None - status: Literal["completed"] - - @model_validator(mode='before') - @classmethod - def _normalize_legacy_status(cls, data: Any) -> Any: - if not isinstance(data, dict): - return data - raw_status = unwrap_enum_value(data.get("status")) - media_buy_status = unwrap_enum_value(data.get("media_buy_status")) - if raw_status is None: - data = dict(data) - data["status"] = "completed" - elif raw_status == "completed": - data = dict(data) - data["status"] = "completed" - elif media_buy_status is None and raw_status in MEDIA_BUY_LEGACY_STATUS_VALUES: - data = dict(data) - data["media_buy_status"] = raw_status - data["status"] = "completed" - elif media_buy_status is not None and raw_status == media_buy_status: - data = dict(data) - data["status"] = "completed" - return data - - -class UpdateMediaBuyResponse2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] - - -class UpdateMediaBuyResponse3(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow', use_enum_values=True, validate_default=True) - status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted - task_id: str - errors: list[error_1.Error] | None = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='before') - @classmethod - def _normalize_submitted_status(cls, data: Any) -> Any: - if isinstance(data, dict) and data.get("status") == "submitted": - data = dict(data) - data["status"] = task_status_1.TaskStatus.submitted - return data - - -UpdateMediaBuyResponse: TypeAlias = ( - UpdateMediaBuyResponse1 | UpdateMediaBuyResponse2 | UpdateMediaBuyResponse3 -) - -__all__ = [ - "UpdateMediaBuyResponse", - "UpdateMediaBuyResponse1", - "UpdateMediaBuyResponse2", - "UpdateMediaBuyResponse3", -] -""", - ) - - _write_if_needed( - "creative/preview_creative_response.py", - "PreviewCreativeResponse", - "class PreviewCreativeResponse1", - common_header - + """ -from . import preview_render as preview_render_1 - - -class PreviewInput(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - name: str | None = None - - -class Preview(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - renders: list[preview_render_1.PreviewRender] - preview_id: str | None = None - input: PreviewInput | None = None - - -class PreviewCreativeResponse1(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - response_type: Literal['single'] | None = None - previews: list[Preview] - expires_at: Any = None - - -class PreviewCreativeResponse2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - response_type: Literal['batch'] | None = None - results: list[Any] - - -class PreviewCreativeResponse3(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - response_type: Literal['variant'] | None = None - variant_id: str | None = None - rendered: list[Any] | None = None - - -PreviewCreativeResponse: TypeAlias = ( - PreviewCreativeResponse1 | PreviewCreativeResponse2 | PreviewCreativeResponse3 -) -""", - ) - - _write_if_needed( - "media_buy/sync_catalogs_response.py", - "SyncCatalogsResponse", - "class SyncCatalogsResponse1", - common_header - + """ -from ..enums import catalog_action - - -class Catalog(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - catalog_id: str - action: catalog_action.CatalogAction - item_count: int | None = None - items_pending: int | None = None - errors: list[error_1.Error] | None = None - - -class SyncCatalogsResponse1(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - catalogs: list[Catalog] - status: Any = None - - -class SyncCatalogsResponse2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] - - -SyncCatalogsResponse: TypeAlias = SyncCatalogsResponse1 | SyncCatalogsResponse2 -""", - ) - - _write_if_needed( - "creative/sync_creatives_response.py", - "SyncCreativesResponse", - "class Creative(", - common_header - + """ -from ..enums import creative_action - - -class Creative(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - creative_id: str - action: creative_action.CreativeAction | str - errors: list[error_1.Error] | None = None - - -class SyncCreativesResponse1(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - creatives: list[Creative] - status: Any = None - - -class SyncCreativesResponse2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] - - -SyncCreativesResponse: TypeAlias = SyncCreativesResponse1 | SyncCreativesResponse2 -""", - ) - - _write_if_needed( - "brand/update_rights_response.py", - "UpdateRightsResponse", - "class UpdateRightsResponse1", - common_header - + """ - -class UpdateRightsResponse1(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - rights_id: str - terms: dict[str, Any] | None = None - - -class UpdateRightsResponse2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] - - -UpdateRightsResponse: TypeAlias = UpdateRightsResponse1 | UpdateRightsResponse2 -""", - ) + continue + schema_rel = _schema_relative(relative) + schema_path = SCHEMA_DIR / schema_rel + if not schema_path.exists(): + print(f" {schema_rel} not found (skipping response arms)") + continue + emitter = Emitter(relative, base, schema_rel) + new_source = emitter.render(_load_schema(schema_rel)) + if target.read_text() != new_source: + target.write_text(new_source) + fixed += 1 + print(f" {relative}: regenerated response arms from schema") + else: + print(f" {relative}: schema-derived response arms already current") - if fixed: - print(f" Restored response variant arms in {fixed} module(s)") - else: - print(" Response variant arms already restored") + print(f" ✓ Regenerated schema-derived response arms: {fixed}") def fix_comply_controller_account_optional() -> None: @@ -3412,6 +3267,9 @@ def main(): fix_reuse_model_discriminator_bug, fix_adagents_duplicate_aliases, restore_format_category_deprecation_shim, + restore_signal_catalog_type_alias, + restore_format_asset_numbered_aliases, + restore_response_variant_aliases, inject_literal_discriminator_defaults, widen_extension_point_lists_to_sequence, fix_canceled_literal_defaults, @@ -3421,9 +3279,6 @@ def main(): fix_product_publisher_property_model_coercion, fix_mcp_webhook_operation_id_optional, fix_signal_listing_range_subclasses, - restore_signal_catalog_type_alias, - restore_format_asset_numbered_aliases, - restore_response_variant_aliases, fix_comply_controller_account_optional, fix_check_governance_status_alias, fix_report_plan_outcome_status_alias, diff --git a/src/adcp/__init__.py b/src/adcp/__init__.py index 4f55205c..442be3e9 100644 --- a/src/adcp/__init__.py +++ b/src/adcp/__init__.py @@ -364,6 +364,7 @@ BothPreviewRender, BuildCreativeErrorResponse, BuildCreativeResponse1, + BuildCreativeSubmittedResponse, BuildCreativeSuccessResponse, CalibrateContentErrorResponse, CalibrateContentResponse1, @@ -441,14 +442,18 @@ SyncAudiencesAudience, SyncAudiencesErrorResponse, SyncAudiencesResponse1, + SyncAudiencesSubmittedResponse, SyncAudiencesSuccessResponse, SyncCatalogResult, SyncCatalogsErrorResponse, SyncCatalogsResponse1, + SyncCatalogsSubmittedResponse, SyncCatalogsSuccessResponse, SyncCreativeResult, SyncCreativesErrorResponse, SyncCreativesResponse1, + SyncCreativesResponse3, + SyncCreativesSubmittedResponse, SyncCreativesSuccessResponse, SyncEventSourcesErrorResponse, SyncEventSourcesResponse1, @@ -1084,6 +1089,7 @@ def get_adcp_version() -> str: "BuildCreativeSuccessResponse", "BuildCreativeResponse1", "BuildCreativeErrorResponse", + "BuildCreativeSubmittedResponse", "CalibrateContentSuccessResponse", "CalibrateContentResponse1", "CalibrateContentErrorResponse", @@ -1149,14 +1155,18 @@ def get_adcp_version() -> str: "SyncAudiencesSuccessResponse", "SyncAudiencesResponse1", "SyncAudiencesErrorResponse", + "SyncAudiencesSubmittedResponse", "SyncCatalogResult", "SyncCatalogsSuccessResponse", "SyncCatalogsResponse1", "SyncCatalogsErrorResponse", + "SyncCatalogsSubmittedResponse", "SyncCreativeResult", "SyncCreativesSuccessResponse", "SyncCreativesResponse1", "SyncCreativesErrorResponse", + "SyncCreativesResponse3", + "SyncCreativesSubmittedResponse", "SyncEventSourcesSuccessResponse", "SyncEventSourcesResponse1", "SyncEventSourcesErrorResponse", diff --git a/src/adcp/server/responses.py b/src/adcp/server/responses.py index 4f887fe1..08889d58 100644 --- a/src/adcp/server/responses.py +++ b/src/adcp/server/responses.py @@ -677,7 +677,7 @@ def build_creative_response( Each manifest should include: format_id, name, assets. Single manifest matches BuildCreativeResponse1. - List matches BuildCreativeResponse2 (multi-format). + List matches BuildCreativeResponse3 (multi-format). """ if isinstance(creative_manifest, list): return { diff --git a/src/adcp/types/__init__.py b/src/adcp/types/__init__.py index 360b6bfa..6d7363ed 100644 --- a/src/adcp/types/__init__.py +++ b/src/adcp/types/__init__.py @@ -499,6 +499,7 @@ BriefFormatAsset, BuildCreativeErrorResponse, BuildCreativeResponse1, + BuildCreativeSubmittedResponse, BuildCreativeSuccessResponse, CalibrateContentErrorResponse, CalibrateContentResponse1, @@ -627,14 +628,18 @@ SyncAudiencesAudience, SyncAudiencesErrorResponse, SyncAudiencesResponse1, + SyncAudiencesSubmittedResponse, SyncAudiencesSuccessResponse, SyncCatalogResult, SyncCatalogsErrorResponse, SyncCatalogsResponse1, + SyncCatalogsSubmittedResponse, SyncCatalogsSuccessResponse, SyncCreativeResult, SyncCreativesErrorResponse, SyncCreativesResponse1, + SyncCreativesResponse3, + SyncCreativesSubmittedResponse, SyncCreativesSuccessResponse, SyncEventSourcesErrorResponse, SyncEventSourcesResponse1, @@ -718,6 +723,7 @@ is_adcp_error, is_adcp_success, is_build_creative_error, + is_build_creative_submitted, is_build_creative_success, is_calibrate_content_success, is_create_media_buy_error, @@ -732,8 +738,10 @@ is_sync_accounts_error, is_sync_accounts_success, is_sync_catalogs_error, + is_sync_catalogs_submitted, is_sync_catalogs_success, is_sync_creatives_error, + is_sync_creatives_submitted, is_sync_creatives_success, is_update_media_buy_error, is_update_media_buy_success, @@ -1341,6 +1349,7 @@ def __init__(self, *args: object, **kwargs: object) -> None: "BothPreviewRender", "BuildCreativeErrorResponse", "BuildCreativeResponse1", + "BuildCreativeSubmittedResponse", "BuildCreativeSuccessResponse", "CalibrateContentErrorResponse", "CalibrateContentResponse1", @@ -1406,9 +1415,12 @@ def __init__(self, *args: object, **kwargs: object) -> None: "SyncCatalogResult", "SyncCatalogsErrorResponse", "SyncCatalogsResponse1", + "SyncCatalogsSubmittedResponse", "SyncCatalogsSuccessResponse", "SyncCreativesErrorResponse", "SyncCreativesResponse1", + "SyncCreativesResponse3", + "SyncCreativesSubmittedResponse", "SyncCreativesSuccessResponse", "SyncCreativeResult", "SyncEventSourcesErrorResponse", @@ -1489,6 +1501,7 @@ def __init__(self, *args: object, **kwargs: object) -> None: # Audiences responses "MediaBuyDeliveryStatus", "SyncAudiencesErrorResponse", + "SyncAudiencesSubmittedResponse", "SyncAudiencesSuccessResponse", # Creative features responses "GetCreativeFeaturesErrorResponse", diff --git a/src/adcp/types/_generated.py b/src/adcp/types/_generated.py index 3573f5c7..601d287f 100644 --- a/src/adcp/types/_generated.py +++ b/src/adcp/types/_generated.py @@ -10,7 +10,7 @@ DO NOT EDIT MANUALLY. Generated from: https://github.com/adcontextprotocol/adcp/tree/main/schemas -Generation date: 2026-06-05 17:01:38 UTC +Generation date: 2026-06-06 18:47:22 UTC """ # ruff: noqa: E501, I001 @@ -223,9 +223,14 @@ GetAccountFinancialsRequest, ) from adcp.types.generated_poc.account.get_account_financials_response import ( + Balance, + Credit, GetAccountFinancialsResponse, GetAccountFinancialsResponse1, GetAccountFinancialsResponse2, + Invoice, + LastTopUp, + Spend, ) from adcp.types.generated_poc.account.list_accounts_request import ListAccountsRequest from adcp.types.generated_poc.account.list_accounts_response import ListAccountsResponse @@ -237,6 +242,8 @@ SyncAccountsRequest, ) from adcp.types.generated_poc.account.sync_accounts_response import ( + CreditLimit, + Setup, SyncAccountsResponse, SyncAccountsResponse1, SyncAccountsResponse2, @@ -300,6 +307,7 @@ AcquireRightsResponse2, AcquireRightsResponse3, AcquireRightsResponse4, + Disclosure, ) from adcp.types.generated_poc.brand.creative_approval_request import CreativeApprovalRequest from adcp.types.generated_poc.brand.creative_approval_response import CreativeApprovalResponse @@ -308,22 +316,29 @@ GetBrandIdentityRequest, ) from adcp.types.generated_poc.brand.get_brand_identity_response import ( - Fallback, + Asset, + Colors, File, - FontRole, FontRole2, + Fonts, GetBrandIdentityResponse, GetBrandIdentityResponse1, GetBrandIdentityResponse2, - OpentypeFeature, - Style, - WeightRangeItem, + House, + Logo, + Rights, + Tone, + VoiceSynthesis, ) from adcp.types.generated_poc.brand.get_rights_request import GetRightsRequest from adcp.types.generated_poc.brand.get_rights_response import ( + Excluded, + ExclusivityStatus, GetRightsResponse, GetRightsResponse1, GetRightsResponse2, + PreviewAsset, + Right, ) from adcp.types.generated_poc.brand.revocation_notification import RevocationNotification from adcp.types.generated_poc.brand.rights_pricing_option import RightsPricingOption @@ -332,11 +347,8 @@ from adcp.types.generated_poc.brand.search_brands_response import ( Background, ExcludedCountry, - House, KellerType, - Logo, Orientation, - Rights, SearchBrandResult, SearchBrandsResponse, ) @@ -476,6 +488,7 @@ CalibrateContentResponse, CalibrateContentResponse1, CalibrateContentResponse2, + Feature, ) from adcp.types.generated_poc.content_standards.content_standards import ( CalibrationExemplars, @@ -503,7 +516,8 @@ TimeRange, ) from adcp.types.generated_poc.content_standards.get_media_buy_artifacts_response import ( - ArtifactRecord, + BrandContext, + CollectionInfo, GetMediaBuyArtifactsResponse, GetMediaBuyArtifactsResponse1, GetMediaBuyArtifactsResponse2, @@ -521,22 +535,17 @@ UpdateContentStandardsResponse, ) from adcp.types.generated_poc.content_standards.validate_content_delivery_request import ( - BrandContext, Record, ValidateContentDeliveryRequest, ) from adcp.types.generated_poc.content_standards.validate_content_delivery_response import ( + Result, + Summary, ValidateContentDeliveryResponse, ValidateContentDeliveryResponse1, ValidateContentDeliveryResponse2, ) -from adcp.types.generated_poc.core.account import ( - Account, - Compression, - CreditLimit, - ReportingBucket, - Setup, -) +from adcp.types.generated_poc.core.account import Account, Compression, ReportingBucket from adcp.types.generated_poc.core.account_authorization import ( AccountAuthorization, AllowedTask, @@ -665,7 +674,6 @@ from adcp.types.generated_poc.core.brand_ref import ( BrandKitOverride, BrandReference, - Colors, DataSubjectContestation, ) from adcp.types.generated_poc.core.business_entity import Address, Bank, BusinessEntity @@ -1009,12 +1017,10 @@ AiTool, C2pa, DeclaredBy, - Disclosure, EmbeddedProvenanceItem, HumanOversight, Provenance, RenderGuidance, - Result, VerificationItem, VerifyAgent, VerifyAgent1585, @@ -1176,7 +1182,6 @@ Range, RefreshCadence, ResolutionMethod, - Right, SeedSource, SignalDefinition, SubjectType, @@ -1387,12 +1392,15 @@ RequestType, ) from adcp.types.generated_poc.creative.preview_creative_response import ( + Input2, Preview, + Preview2, + Preview3, PreviewCreativeResponse, PreviewCreativeResponse1, PreviewCreativeResponse2, PreviewCreativeResponse3, - PreviewInput, + Response, ) from adcp.types.generated_poc.creative.preview_render import ( Embedding, @@ -1409,6 +1417,7 @@ SyncCreativesResponse, SyncCreativesResponse1, SyncCreativesResponse2, + SyncCreativesResponse3, ) from adcp.types.generated_poc.creative.validate_input_request import ( Targets, @@ -1549,7 +1558,6 @@ GovernedAction, Plan, Statuses, - Summary, Thresholds, ) from adcp.types.generated_poc.governance.policy_category_definition import ( @@ -1599,6 +1607,7 @@ Dimension, KeepMode, MaxSpend, + PreviewInput, SignalCondition, SignalCondition1, SignalCondition2, @@ -1613,6 +1622,15 @@ BuildCreativeResponse, BuildCreativeResponse1, BuildCreativeResponse2, + BuildCreativeResponse3, + BuildCreativeResponse4, + BuildCreativeResponse5, + BuildCreativeResponse6, + CatalogItemRef, + Estimate, + Eval, + PerLeaf, + Preview4, ) from adcp.types.generated_poc.media_buy.create_media_buy_request import ( ArtifactWebhook, @@ -1685,6 +1703,7 @@ LogEventResponse, LogEventResponse1, LogEventResponse2, + PartialFailure, ) from adcp.types.generated_poc.media_buy.media_buy_delivery_webhook_result import ( MediaBuyDeliveryWebhookResult, @@ -1715,15 +1734,19 @@ SyncAudiencesRequest, ) from adcp.types.generated_poc.media_buy.sync_audiences_response import ( + MatchBreakdown, SyncAudiencesResponse, SyncAudiencesResponse1, SyncAudiencesResponse2, + SyncAudiencesResponse3, ) from adcp.types.generated_poc.media_buy.sync_catalogs_request import SyncCatalogsRequest from adcp.types.generated_poc.media_buy.sync_catalogs_response import ( + ItemIssue, SyncCatalogsResponse, SyncCatalogsResponse1, SyncCatalogsResponse2, + SyncCatalogsResponse3, ) from adcp.types.generated_poc.media_buy.sync_event_sources_request import SyncEventSourcesRequest from adcp.types.generated_poc.media_buy.sync_event_sources_response import ( @@ -1804,11 +1827,7 @@ AuthorizationSummary, ValidatePropertyDeliveryResponse, ) -from adcp.types.generated_poc.property.validation_result import ( - Feature, - Requirement, - ValidationResult, -) +from adcp.types.generated_poc.property.validation_result import Requirement, ValidationResult from adcp.types.generated_poc.protocol.get_adcp_capabilities_request import ( GetAdcpCapabilitiesRequest, ) @@ -1917,7 +1936,6 @@ SiInitiateSessionRequest, ) from adcp.types.generated_poc.sponsored_intelligence.si_initiate_session_response import ( - Response, SiInitiateSessionResponse, ) from adcp.types.generated_poc.sponsored_intelligence.si_send_message_request import ( @@ -1977,6 +1995,9 @@ from adcp.types.generated_poc.media_buy.sync_audiences_request import ( Audience as _AudienceFromSyncAudiencesRequest, ) +from adcp.types.generated_poc.media_buy.sync_audiences_response import ( + Audience as _AudienceFromSyncAudiencesResponse, +) from adcp.types.generated_poc.core.error import Error as _ErrorFromError # Backward-compatible adagents authorization variant numbering @@ -2116,11 +2137,11 @@ "Arm", "Art9Basis", "Artifact", - "ArtifactRecord", "ArtifactRef", "ArtifactWebhook", "ArtifactWebhookPayload", "AssessmentStatus", + "Asset", "AssetAccess", "AssetAccess1", "AssetAccess2", @@ -2222,6 +2243,7 @@ "Avatar", "Background", "BadgeRole", + "Balance", "Bank", "BaseCollectionSource", "BaseCollectionSource1", @@ -2265,6 +2287,10 @@ "BuildCreativeResponse", "BuildCreativeResponse1", "BuildCreativeResponse2", + "BuildCreativeResponse3", + "BuildCreativeResponse4", + "BuildCreativeResponse5", + "BuildCreativeResponse6", "BuildCreativeSubmitted", "BuildCreativeWorking", "BusinessEntity", @@ -2319,6 +2345,7 @@ "CatalogFieldBinding1", "CatalogFieldMapping", "CatalogItemDeliveryMetrics", + "CatalogItemRef", "CatalogItemStatus", "CatalogMatch", "CatalogRequirements", @@ -2355,6 +2382,7 @@ "Collection", "CollectionCadence", "CollectionDistribution", + "CollectionInfo", "CollectionKind", "CollectionList", "CollectionListChangedWebhook", @@ -2482,6 +2510,7 @@ "CreativeStatusChangedWebhook", "CreativeVariable", "CreativeVariant", + "Credit", "CreditLimit", "CssAsset", "CssAssetRequirements", @@ -2587,6 +2616,8 @@ "Escalation", "EscalationRateTrend", "EscalationSeverity", + "Estimate", + "Eval", "EvalBudget", "EvaluatorSpec", "EvaluatorSpec1", @@ -2600,9 +2631,11 @@ "EventType", "Evidence", "ExcludeDistributionId", + "Excluded", "ExcludedBy", "ExcludedCountry", "Exclusivity", + "ExclusivityStatus", "Execution", "Exemplar", "Exemplars", @@ -2613,7 +2646,6 @@ "Extensions", "ExtensionsSupportedItem", "Fail", - "Fallback", "FanoutMode", "Feature", "FeatureAgent", @@ -2633,8 +2665,8 @@ "Flight", "FlightItem", "FollowUp", - "FontRole", "FontRole2", + "Fonts", "ForecastMethod", "ForecastPoint", "ForecastPointDimensions", @@ -2770,6 +2802,7 @@ "IndustryIdentifier", "Initiator", "Input", + "Input2", "Input5", "InsertionOrder", "Installment", @@ -2777,8 +2810,10 @@ "InstallmentStatus", "IntegrationAction", "Intent", + "Invoice", "IoAcceptance", "Issue", + "ItemIssue", "ItemProductionModel", "Items", "JavascriptAsset", @@ -2799,6 +2834,7 @@ "Kind", "LandingPageRequirement", "LanguageItem", + "LastTopUp", "Layout", "Level", "LiftDimension", @@ -2842,6 +2878,7 @@ "MarkdownAssetRequirements", "MarkdownFlavor", "Market", + "MatchBreakdown", "MatchIdType", "MatchKey", "MatchType", @@ -2909,7 +2946,6 @@ "OfferingAssetGroup", "OfferingAvailabilityStatus", "Onboarder", - "OpentypeFeature", "Operator", "OptimizationGoal", "OptimizationGoal1", @@ -2943,6 +2979,7 @@ "Parameters", "Params", "ParentMatchBehavior", + "PartialFailure", "Pass", "Payload", "Payload1", @@ -2967,6 +3004,7 @@ "Payload9", "PaymentTerms", "PerItemBindings", + "PerLeaf", "PerformanceFeedback", "PerformanceStandard", "PerformanceStandardMetric", @@ -2995,6 +3033,10 @@ "PreOnboardingPrecisionLevel", "Presence", "Preview", + "Preview2", + "Preview3", + "Preview4", + "PreviewAsset", "PreviewCreativeRequest", "PreviewCreativeResponse", "PreviewCreativeResponse1", @@ -3284,6 +3326,7 @@ "SpecialCategory", "Specialisms", "Specification", + "Spend", "SponsoredIntelligence", "SponsoredPlacementType", "StaleResponseDetails", @@ -3301,7 +3344,6 @@ "StoryboardStatus", "StringArray", "Structural", - "Style", "SubjectType", "Summary", "SupportedCatalogType", @@ -3327,11 +3369,13 @@ "SyncAudiencesResponse", "SyncAudiencesResponse1", "SyncAudiencesResponse2", + "SyncAudiencesResponse3", "SyncCatalogsInputRequired", "SyncCatalogsRequest", "SyncCatalogsResponse", "SyncCatalogsResponse1", "SyncCatalogsResponse2", + "SyncCatalogsResponse3", "SyncCatalogsSubmitted", "SyncCatalogsWorking", "SyncCreativesInputRequired", @@ -3339,6 +3383,7 @@ "SyncCreativesResponse", "SyncCreativesResponse1", "SyncCreativesResponse2", + "SyncCreativesResponse3", "SyncCreativesSubmitted", "SyncCreativesWorking", "SyncEventSourcesRequest", @@ -3392,6 +3437,7 @@ "TmpProviderRegistration1", "TmpProviderRegistration2", "TmpResponseType", + "Tone", "Tools", "TotalBudget", "TotalBudgetCap", @@ -3541,6 +3587,7 @@ "Violation", "Visual", "Voice", + "VoiceSynthesis", "VoiceSynthesisRefItem", "VpaidVersion", "Watermark", @@ -3554,7 +3601,6 @@ "WebhookResponseType", "WebhookSecurityMethod", "WebhookSigning", - "WeightRangeItem", "WholesaleFeedEvent", "WholesaleFeedEvent1", "WholesaleFeedEvent2", @@ -3573,6 +3619,7 @@ "ZipAsset", "_AudienceFromGetMediaBuyDeliveryRequest", "_AudienceFromSyncAudiencesRequest", + "_AudienceFromSyncAudiencesResponse", "_DeliveryStatusFromGetMediaBuyDeliveryResponse", "_ErrorFromError", "_PackageFromPackage", diff --git a/src/adcp/types/aliases.py b/src/adcp/types/aliases.py index 474623e9..941d91a8 100644 --- a/src/adcp/types/aliases.py +++ b/src/adcp/types/aliases.py @@ -92,6 +92,18 @@ VastAsset2, VcpmPricingOption, ) +from adcp.types._generated import ( + BuildCreativeResponse3 as _BuildCreativeResponse3, +) +from adcp.types._generated import ( + BuildCreativeResponse4 as _BuildCreativeResponse4, +) +from adcp.types._generated import ( + BuildCreativeResponse5 as _BuildCreativeResponse5, +) +from adcp.types._generated import ( + BuildCreativeResponse6 as _BuildCreativeResponse6, +) from adcp.types.generated_poc.core.error import ( Recovery, Source, @@ -118,6 +130,10 @@ def _generated_alias(name: str, fallback_name: str) -> Any: ActivateSignalResponse2 = _generated_alias("ActivateSignalResponse2", "ActivateSignalResponse") BuildCreativeResponse1 = _generated_alias("BuildCreativeResponse1", "BuildCreativeResponse") BuildCreativeResponse2 = _generated_alias("BuildCreativeResponse2", "BuildCreativeResponse") +BuildCreativeResponse3: TypeAlias = _BuildCreativeResponse3 +BuildCreativeResponse4: TypeAlias = _BuildCreativeResponse4 +BuildCreativeResponse5: TypeAlias = _BuildCreativeResponse5 +BuildCreativeResponse6: TypeAlias = _BuildCreativeResponse6 CalibrateContentResponse1 = _generated_alias( "CalibrateContentResponse1", "CalibrateContentResponse" ) @@ -195,10 +211,13 @@ def _generated_alias(name: str, fallback_name: str) -> Any: SyncAccountsResponse2 = _generated_alias("SyncAccountsResponse2", "SyncAccountsResponse") SyncAudiencesResponse1 = _generated_alias("SyncAudiencesResponse1", "SyncAudiencesResponse") SyncAudiencesResponse2 = _generated_alias("SyncAudiencesResponse2", "SyncAudiencesResponse") +SyncAudiencesResponse3 = _generated_alias("SyncAudiencesResponse3", "SyncAudiencesResponse") SyncCatalogsResponse1 = _generated_alias("SyncCatalogsResponse1", "SyncCatalogsResponse") SyncCatalogsResponse2 = _generated_alias("SyncCatalogsResponse2", "SyncCatalogsResponse") +SyncCatalogsResponse3 = _generated_alias("SyncCatalogsResponse3", "SyncCatalogsResponse") SyncCreativesResponse1 = _generated_alias("SyncCreativesResponse1", "SyncCreativesResponse") SyncCreativesResponse2 = _generated_alias("SyncCreativesResponse2", "SyncCreativesResponse") +SyncCreativesResponse3 = _generated_alias("SyncCreativesResponse3", "SyncCreativesResponse") SyncEventSourcesResponse1 = _generated_alias( "SyncEventSourcesResponse1", "SyncEventSourcesResponse" ) @@ -457,6 +476,9 @@ def _generated_alias(name: str, fallback_name: str) -> Any: BuildCreativeErrorResponse: TypeAlias = BuildCreativeResponse2 """Error response - creative build failed, no manifest created.""" +BuildCreativeSubmittedResponse: TypeAlias = BuildCreativeResponse6 +"""Submitted (async) envelope - creative build accepted for async processing.""" + # Create Media Buy Response Variants CreateMediaBuySuccessResponse = CreateMediaBuyResponse1 """Success response - media buy created successfully with media_buy_id.""" @@ -492,6 +514,9 @@ def _generated_alias(name: str, fallback_name: str) -> Any: SyncCreativesErrorResponse: TypeAlias = SyncCreativesResponse2 """Error response - sync operation failed.""" +SyncCreativesSubmittedResponse: TypeAlias = SyncCreativesResponse3 +"""Submitted (async) envelope - creative sync accepted for async processing.""" + # Sync Creative Result (nested type from SyncCreativesResponse1.creatives[]) SyncCreativeResult: TypeAlias = SyncCreativeResultInternal """Result of syncing a single creative - indicates action taken (created, updated, failed, etc.) @@ -530,6 +555,9 @@ def process_result(result: SyncCreativeResult) -> None: SyncCatalogsErrorResponse: TypeAlias = SyncCatalogsResponse2 """Error response - operation failed completely, no catalogs were processed.""" +SyncCatalogsSubmittedResponse: TypeAlias = SyncCatalogsResponse3 +"""Submitted (async) envelope - catalog sync accepted for async processing.""" + # Sync Catalog Result (nested type from SyncCatalogsResponse1.catalogs[]) SyncCatalogResult: TypeAlias = SyncCatalogResultInternal """Result of syncing a single catalog - indicates action taken and per-item status. @@ -627,6 +655,9 @@ def process_result(result: SyncCatalogResult) -> None: SyncAudiencesErrorResponse: TypeAlias = SyncAudiencesResponse2 """Error response - audiences sync failed.""" +SyncAudiencesSubmittedResponse: TypeAlias = SyncAudiencesResponse3 +"""Submitted (async) envelope - audience sync accepted for async processing.""" + # Get Creative Features Response Variants GetCreativeFeaturesSuccessResponse: TypeAlias = GetCreativeFeaturesResponse1 """Success response - creative features retrieved.""" @@ -1828,8 +1859,13 @@ class UnknownGroupAsset(_BaseGroupAsset): # Authorized agent union "AuthorizedAgent", # Build creative responses + "BuildCreativeResponse3", + "BuildCreativeResponse4", + "BuildCreativeResponse5", + "BuildCreativeResponse6", "BuildCreativeSuccessResponse", "BuildCreativeErrorResponse", + "BuildCreativeSubmittedResponse", # Calibrate content responses "CalibrateContentSuccessResponse", "CalibrateContentErrorResponse", @@ -1883,11 +1919,13 @@ class UnknownGroupAsset(_BaseGroupAsset): # Sync creatives responses "SyncCreativesSuccessResponse", "SyncCreativesErrorResponse", + "SyncCreativesSubmittedResponse", "SyncCreativeResult", # Sync catalogs responses "SyncCatalogResult", "SyncCatalogsSuccessResponse", "SyncCatalogsErrorResponse", + "SyncCatalogsSubmittedResponse", # Sync event sources responses "SyncEventSourcesSuccessResponse", "SyncEventSourcesErrorResponse", @@ -1908,6 +1946,7 @@ class UnknownGroupAsset(_BaseGroupAsset): # Sync audiences responses "SyncAudiencesSuccessResponse", "SyncAudiencesErrorResponse", + "SyncAudiencesSubmittedResponse", # Get creative features responses "GetCreativeFeaturesSuccessResponse", "GetCreativeFeaturesErrorResponse", diff --git a/src/adcp/types/generated_poc/account/get_account_financials_response.py b/src/adcp/types/generated_poc/account/get_account_financials_response.py index 8fe8aa91..2d5f405d 100644 --- a/src/adcp/types/generated_poc/account/get_account_financials_response.py +++ b/src/adcp/types/generated_poc/account/get_account_financials_response.py @@ -1,30 +1,92 @@ # generated by datamodel-codegen: # filename: account/get_account_financials_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations +from datetime import date + +from typing import Annotated, Literal, TypeAlias + +from pydantic import ConfigDict, Field, StringConstraints + from ..core.version_envelope import AdcpVersionEnvelope +from ..core import account_ref as account_ref_1 +from ..core import context as context_1 +from ..core import date_range as date_range_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..enums import payment_terms as payment_terms_1 -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias +class Spend(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + total_spend: Annotated[float, Field(ge=0)] + media_buy_count: Annotated[int, Field(ge=0)] | None = None -from pydantic import ConfigDict -from ..core import error as error_1 +class Credit(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + credit_limit: Annotated[float, Field(ge=0)] + available_credit: float + utilization_percent: Annotated[float, Field(ge=0, le=100)] | None = None + + +class LastTopUp(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + amount: Annotated[float, Field(ge=0)] + date: date + + +class Balance(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + available: Annotated[float, Field(ge=0)] + last_top_up: LastTopUp | None = None + + +class Invoice(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + invoice_id: str + period: date_range_1.DateRange | None = None + amount: Annotated[float, Field(ge=0)] + status: Literal['draft', 'issued', 'paid', 'past_due', 'void'] + due_date: date | None = None + paid_date: date | None = None class GetAccountFinancialsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - status: Any = None + account: account_ref_1.AccountReference + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] + period: date_range_1.DateRange + timezone: str + spend: Spend | None = None + credit: Credit | None = None + balance: Balance | None = None + payment_status: Literal['current', 'past_due', 'suspended'] | None = None + payment_terms: payment_terms_1.PaymentTerms | None = None + invoices: list[Invoice] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class GetAccountFinancialsResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None GetAccountFinancialsResponse: TypeAlias = GetAccountFinancialsResponse1 | GetAccountFinancialsResponse2 + + +__all__ = [ + 'GetAccountFinancialsResponse', + 'GetAccountFinancialsResponse1', + 'GetAccountFinancialsResponse2', + 'Balance', + 'Credit', + 'Invoice', + 'LastTopUp', + 'Spend', +] diff --git a/src/adcp/types/generated_poc/account/sync_accounts_response.py b/src/adcp/types/generated_poc/account/sync_accounts_response.py index bb84465d..849c103e 100644 --- a/src/adcp/types/generated_poc/account/sync_accounts_response.py +++ b/src/adcp/types/generated_poc/account/sync_accounts_response.py @@ -1,30 +1,84 @@ # generated by datamodel-codegen: # filename: account/sync_accounts_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations +from typing import Annotated, Literal, TypeAlias + +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StringConstraints + from ..core.version_envelope import AdcpVersionEnvelope +from ..core import account_authorization as account_authorization_1 +from ..core import brand_ref as brand_ref_1 +from ..core import business_entity as business_entity_1 +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core import notification_config as notification_config_1 +from ..enums import account_scope as account_scope_1 +from ..enums import billing_party as billing_party_1 +from ..enums import payment_terms as payment_terms_1 -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias +class Setup(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + url: AnyUrl | None = None + message: str + expires_at: AwareDatetime | None = None -from pydantic import ConfigDict -from ..core import error as error_1 +class CreditLimit(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + amount: Annotated[float, Field(ge=0)] + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] + + +class Account(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + account_id: str | None = None + brand: brand_ref_1.BrandReference + operator: str + name: str | None = None + action: Literal['created', 'updated', 'unchanged', 'failed'] + status: Literal['active', 'pending_approval', 'rejected', 'payment_required', 'suspended', 'closed'] + billing: billing_party_1.BillingParty | None = None + billing_entity: business_entity_1.BusinessEntity | None = None + account_scope: account_scope_1.AccountScope | None = None + setup: Setup | None = None + rate_card: str | None = None + payment_terms: payment_terms_1.PaymentTerms | None = None + credit_limit: CreditLimit | None = None + errors: list[error_1.Error] | None = None + warnings: list[str] | None = None + sandbox: bool | None = None + notification_configs: Annotated[list[notification_config_1.NotificationConfig], Field(max_length=16)] | None = None + authorization: account_authorization_1.AccountAuthorization | None = None class SyncAccountsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - status: Any = None + dry_run: bool | None = None + accounts: list[Account] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class SyncAccountsResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None SyncAccountsResponse: TypeAlias = SyncAccountsResponse1 | SyncAccountsResponse2 + + +__all__ = [ + 'SyncAccountsResponse', + 'SyncAccountsResponse1', + 'SyncAccountsResponse2', + 'Account', + 'CreditLimit', + 'Setup', +] diff --git a/src/adcp/types/generated_poc/brand/acquire_rights_response.py b/src/adcp/types/generated_poc/brand/acquire_rights_response.py index d3d4d6f8..0846b890 100644 --- a/src/adcp/types/generated_poc/brand/acquire_rights_response.py +++ b/src/adcp/types/generated_poc/brand/acquire_rights_response.py @@ -1,58 +1,82 @@ # generated by datamodel-codegen: # filename: brand/acquire_rights_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations -from ..core.version_envelope import AdcpVersionEnvelope +from typing import Annotated, Literal, TypeAlias +from pydantic import AnyUrl, ConfigDict, Field -from typing import Any, Literal, TypeAlias +from ..core.version_envelope import AdcpVersionEnvelope +from . import rights_terms as rights_terms_1 +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core import generation_credential as generation_credential_1 +from ..core import push_notification_config as push_notification_config_1 +from ..core import rights_constraint as rights_constraint_1 -from pydantic import ConfigDict -from ..core import error as error_1 +class Disclosure(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + required: bool + text: str | None = None class AcquireRightsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') rights_id: str + rights_status: Literal['acquired'] = 'acquired' brand_id: str - terms: Any - generation_credentials: list[Any] - rights_constraint: Any - rights_status: Literal['acquired'] | None = None - status: Literal['acquired'] | None = None + terms: rights_terms_1.RightsTerms + generation_credentials: list[generation_credential_1.GenerationCredential] + restrictions: list[str] | None = None + disclosure: Disclosure | None = None + approval_webhook: push_notification_config_1.PushNotificationConfig | None = None + usage_reporting_url: AnyUrl | None = None + rights_constraint: rights_constraint_1.RightsConstraint + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class AcquireRightsResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') rights_id: str + rights_status: Literal['pending_approval'] = 'pending_approval' brand_id: str - rights_status: Literal['pending_approval'] | None = None - status: Literal['pending_approval'] | None = None detail: str | None = None estimated_response_time: str | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class AcquireRightsResponse3(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') rights_id: str + rights_status: Literal['rejected'] = 'rejected' brand_id: str reason: str - rights_status: Literal['rejected'] | None = None - status: Literal['rejected'] | None = None suggestions: list[str] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class AcquireRightsResponse4(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +AcquireRightsResponse: TypeAlias = AcquireRightsResponse1 | AcquireRightsResponse2 | AcquireRightsResponse3 | AcquireRightsResponse4 -AcquireRightsResponse: TypeAlias = ( - AcquireRightsResponse1 - | AcquireRightsResponse2 - | AcquireRightsResponse3 - | AcquireRightsResponse4 -) +__all__ = [ + 'AcquireRightsResponse', + 'AcquireRightsResponse1', + 'AcquireRightsResponse2', + 'AcquireRightsResponse3', + 'AcquireRightsResponse4', + 'Disclosure', +] diff --git a/src/adcp/types/generated_poc/brand/get_brand_identity_response.py b/src/adcp/types/generated_poc/brand/get_brand_identity_response.py index 8c9e0462..d159f417 100644 --- a/src/adcp/types/generated_poc/brand/get_brand_identity_response.py +++ b/src/adcp/types/generated_poc/brand/get_brand_identity_response.py @@ -1,110 +1,153 @@ # generated by datamodel-codegen: # filename: brand/get_brand_identity_response.json -# timestamp: 2026-05-27T10:05:00+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations -from adcp.types._str_enum import StrEnum -from typing import Any, Annotated +from typing import Annotated, Any, Literal, TypeAlias -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel +from pydantic import AnyUrl, ConfigDict, Field, StringConstraints from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..enums import asset_content_type as asset_content_type_1 +from ..enums import right_use as right_use_1 -class WeightRangeItem(RootModel[int]): - root: Annotated[int, Field(ge=100, le=900)] +class House(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + domain: str + name: str -class Style(StrEnum): - normal = 'normal' - italic = 'italic' - oblique = 'oblique' +class Logo(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + url: AnyUrl + orientation: Literal['square', 'horizontal', 'vertical', 'stacked'] | None = None + background: Literal['dark-bg', 'light-bg', 'transparent-bg'] | None = None + variant: Literal['primary', 'secondary', 'icon', 'wordmark', 'full-lockup'] | None = None + tags: list[str] | None = None + usage: str | None = None + width: int | None = None + height: int | None = None -class File(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[AnyUrl, Field(description='HTTPS URL to the font file')] - weight: Annotated[int | None, Field(description='CSS numeric font-weight', ge=100, le=900)] = ( - None - ) - weight_range: Annotated[ - list[WeightRangeItem] | None, - Field( - description='Variable font weight axis range as [min, max]', max_length=2, min_length=2 - ), - ] = None - style: Annotated[Style | None, Field(description='CSS font-style')] = None +class Colors(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + primary: Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')] | Annotated[list[Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')]], Field(min_length=1)] | None = None + secondary: Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')] | Annotated[list[Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')]], Field(min_length=1)] | None = None + accent: Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')] | Annotated[list[Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')]], Field(min_length=1)] | None = None + background: Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')] | Annotated[list[Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')]], Field(min_length=1)] | None = None + text: Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')] | Annotated[list[Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')]], Field(min_length=1)] | None = None -class OpentypeFeature(RootModel[str]): - root: Annotated[str, Field(pattern='^[a-z0-9]{4}$')] +class File(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + url: Annotated[str, StringConstraints(pattern='^https://')] + weight: Annotated[int, Field(ge=100, le=900)] | None = None + weight_range: Annotated[list[Annotated[int, Field(ge=100, le=900)]], Field(min_length=2, max_length=2)] | None = None + style: Literal['normal', 'italic', 'oblique'] | None = None -class Fallback(RootModel[str]): - root: Annotated[str, Field(max_length=100)] +class FontRole2(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + family: str + files: Annotated[list[File], Field(max_length=36)] | None = None + opentype_features: Annotated[list[Annotated[str, StringConstraints(pattern='^[a-z0-9]{4}$')]], Field(max_length=20)] | None = None + fallbacks: Annotated[list[Annotated[str, StringConstraints(max_length=100)]], Field(max_length=10)] | None = None -class FontRole2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - family: Annotated[str, Field(description='CSS font-family name')] - files: Annotated[list[File] | None, Field(max_length=36)] = None - opentype_features: Annotated[ - list[OpentypeFeature] | None, - Field( - description="OpenType feature tags to enable (e.g., ['ss01', 'tnum'])", max_length=20 - ), - ] = None - fallbacks: Annotated[ - list[Fallback] | None, - Field(description='Ordered fallback font-family names for script coverage', max_length=10), - ] = None +class Fonts(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + primary: str | FontRole2 | None = None + secondary: str | FontRole2 | None = None -class FontRole(RootModel[str | FontRole2]): - root: str | FontRole2 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +class Tone(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + voice: str | None = None + attributes: list[str] | None = None + dos: list[str] | None = None + donts: list[str] | None = None -from typing import TypeAlias +class VoiceSynthesis(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + provider: str | None = None + voice_id: str | None = None + settings: dict[str, Any] | None = None -from pydantic import ConfigDict -from ..core import error as error_1 +class Asset(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + asset_id: str + asset_type: asset_content_type_1.AssetContentType + url: AnyUrl + tags: list[str] | None = None + name: str | None = None + description: str | None = None + width: int | None = None + height: int | None = None + duration_seconds: float | None = None + file_size_bytes: int | None = None + format: str | None = None + + +class Rights(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + available_uses: list[right_use_1.RightUse] | None = None + countries: list[Annotated[str, StringConstraints(pattern='^[A-Z]{2}$')]] | None = None + excluded_countries: list[Annotated[str, StringConstraints(pattern='^[A-Z]{2}$')]] | None = None + exclusivity_model: str | None = None + content_restrictions: list[str] | None = None class GetBrandIdentityResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') brand_id: str - house: str - names: dict[str, Any] + house: House + names: list[dict[str, str]] description: str | None = None - tagline: str | None = None - industries: list[str] | None = None - tone: Any = None - visual_guidelines: Any = None - colors: Any = None - logos: Any = None - fonts: Any = None - assets: Any = None - rights: Any = None - voice_synthesis: Any = None - keller_type: Any = None - available_fields: list[str] | None = None + industries: Annotated[list[str], Field(min_length=1)] | None = None + keller_type: Literal['master', 'sub_brand', 'endorsed', 'independent'] | None = None + logos: list[Logo] | None = None + colors: Colors | None = None + fonts: Fonts | None = None + visual_guidelines: dict[str, Any] | None = None + tone: Tone | None = None + tagline: str | Annotated[list[dict[str, Annotated[str, StringConstraints(min_length=1)]]], Field(min_length=1)] | None = None + voice_synthesis: VoiceSynthesis | None = None + assets: list[Asset] | None = None + rights: Rights | None = None + available_fields: list[Literal['description', 'industries', 'keller_type', 'logos', 'colors', 'fonts', 'visual_guidelines', 'tone', 'tagline', 'voice_synthesis', 'assets', 'rights']] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class GetBrandIdentityResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None GetBrandIdentityResponse: TypeAlias = GetBrandIdentityResponse1 | GetBrandIdentityResponse2 + + +__all__ = [ + 'GetBrandIdentityResponse', + 'GetBrandIdentityResponse1', + 'GetBrandIdentityResponse2', + 'Asset', + 'Colors', + 'File', + 'FontRole2', + 'Fonts', + 'House', + 'Logo', + 'Rights', + 'Tone', + 'VoiceSynthesis', +] diff --git a/src/adcp/types/generated_poc/brand/get_rights_response.py b/src/adcp/types/generated_poc/brand/get_rights_response.py index 370cf9a2..2b01adac 100644 --- a/src/adcp/types/generated_poc/brand/get_rights_response.py +++ b/src/adcp/types/generated_poc/brand/get_rights_response.py @@ -1,30 +1,84 @@ # generated by datamodel-codegen: # filename: brand/get_rights_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations +from typing import Annotated, TypeAlias + +from pydantic import AnyUrl, ConfigDict, Field, StringConstraints + from ..core.version_envelope import AdcpVersionEnvelope +from . import rights_pricing_option as rights_pricing_option_1 +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..enums import right_type as right_type_1 +from ..enums import right_use as right_use_1 -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias +class ExclusivityStatus(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + available: bool | None = None + existing_exclusives: list[str] | None = None -from pydantic import ConfigDict -from ..core import error as error_1 +class PreviewAsset(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + url: AnyUrl + usage: str | None = None + + +class Right(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + rights_id: str + brand_id: str + name: str + description: str | None = None + right_type: right_type_1.RightType | None = None + match_score: Annotated[float, Field(ge=0, le=1)] | None = None + match_reasons: list[str] | None = None + available_uses: list[right_use_1.RightUse] + countries: list[Annotated[str, StringConstraints(pattern='^[A-Z]{2}$')]] | None = None + excluded_countries: list[Annotated[str, StringConstraints(pattern='^[A-Z]{2}$')]] | None = None + exclusivity_status: ExclusivityStatus | None = None + pricing_options: Annotated[list[rights_pricing_option_1.RightsPricingOption], Field(min_length=1)] + content_restrictions: list[str] | None = None + preview_assets: list[PreviewAsset] | None = None + + +class Excluded(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + brand_id: str + name: str | None = None + reason: str + suggestions: list[str] | None = None class GetRightsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - status: Any = None + rights: list[Right] + excluded: list[Excluded] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class GetRightsResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None GetRightsResponse: TypeAlias = GetRightsResponse1 | GetRightsResponse2 + + +__all__ = [ + 'GetRightsResponse', + 'GetRightsResponse1', + 'GetRightsResponse2', + 'Excluded', + 'ExclusivityStatus', + 'PreviewAsset', + 'Right', +] diff --git a/src/adcp/types/generated_poc/brand/update_rights_response.py b/src/adcp/types/generated_poc/brand/update_rights_response.py index 3dff892e..5c1a2fb1 100644 --- a/src/adcp/types/generated_poc/brand/update_rights_response.py +++ b/src/adcp/types/generated_poc/brand/update_rights_response.py @@ -1,31 +1,46 @@ # generated by datamodel-codegen: # filename: brand/update_rights_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations -from ..core.version_envelope import AdcpVersionEnvelope - - -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias +from typing import Annotated, TypeAlias -from pydantic import ConfigDict +from pydantic import AwareDatetime, ConfigDict, Field +from ..core.version_envelope import AdcpVersionEnvelope +from . import rights_terms as rights_terms_1 +from ..core import context as context_1 from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core import generation_credential as generation_credential_1 +from ..core import rights_constraint as rights_constraint_1 class UpdateRightsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') rights_id: str - terms: dict[str, Any] | None = None + terms: rights_terms_1.RightsTerms + generation_credentials: list[generation_credential_1.GenerationCredential] | None = None + rights_constraint: rights_constraint_1.RightsConstraint | None = None + paused: bool | None = None + implementation_date: AwareDatetime | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class UpdateRightsResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None UpdateRightsResponse: TypeAlias = UpdateRightsResponse1 | UpdateRightsResponse2 + + +__all__ = [ + 'UpdateRightsResponse', + 'UpdateRightsResponse1', + 'UpdateRightsResponse2', +] diff --git a/src/adcp/types/generated_poc/content_standards/calibrate_content_response.py b/src/adcp/types/generated_poc/content_standards/calibrate_content_response.py index 71ec0d28..669bfc5f 100644 --- a/src/adcp/types/generated_poc/content_standards/calibrate_content_response.py +++ b/src/adcp/types/generated_poc/content_standards/calibrate_content_response.py @@ -1,30 +1,53 @@ # generated by datamodel-codegen: # filename: content_standards/calibrate_content_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations -from ..core.version_envelope import AdcpVersionEnvelope +from typing import Annotated, TypeAlias +from pydantic import ConfigDict, Field -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..enums import binary_verdict as binary_verdict_1 +from ..enums import feature_check_status as feature_check_status_1 -from pydantic import ConfigDict -from ..core import error as error_1 +class Feature(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + feature_id: str + status: feature_check_status_1.FeatureCheckStatus + policy_id: str | None = None + explanation: str | None = None + confidence: Annotated[float, Field(ge=0, le=1)] | None = None class CalibrateContentResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - status: Any = None + verdict: binary_verdict_1.BinaryVerdict + confidence: Annotated[float, Field(ge=0, le=1)] | None = None + explanation: str | None = None + features: list[Feature] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class CalibrateContentResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: list[error_1.Error] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None CalibrateContentResponse: TypeAlias = CalibrateContentResponse1 | CalibrateContentResponse2 + + +__all__ = [ + 'CalibrateContentResponse', + 'CalibrateContentResponse1', + 'CalibrateContentResponse2', + 'Feature', +] diff --git a/src/adcp/types/generated_poc/content_standards/get_content_standards_response.py b/src/adcp/types/generated_poc/content_standards/get_content_standards_response.py index 15721442..06b885e3 100644 --- a/src/adcp/types/generated_poc/content_standards/get_content_standards_response.py +++ b/src/adcp/types/generated_poc/content_standards/get_content_standards_response.py @@ -1,28 +1,37 @@ # generated by datamodel-codegen: # filename: content_standards/get_content_standards_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations -from ..core.version_envelope import AdcpVersionEnvelope - - from typing import TypeAlias from pydantic import ConfigDict +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 from ..core import error as error_1 +from ..core import ext as ext_1 class GetContentStandardsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class GetContentStandardsResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: list[error_1.Error] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +GetContentStandardsResponse: TypeAlias = GetContentStandardsResponse1 | GetContentStandardsResponse2 -GetContentStandardsResponse: TypeAlias = ( - GetContentStandardsResponse1 | GetContentStandardsResponse2 -) +__all__ = [ + 'GetContentStandardsResponse', + 'GetContentStandardsResponse1', + 'GetContentStandardsResponse2', +] diff --git a/src/adcp/types/generated_poc/content_standards/get_media_buy_artifacts_response.py b/src/adcp/types/generated_poc/content_standards/get_media_buy_artifacts_response.py index 1605f223..4639ef1f 100644 --- a/src/adcp/types/generated_poc/content_standards/get_media_buy_artifacts_response.py +++ b/src/adcp/types/generated_poc/content_standards/get_media_buy_artifacts_response.py @@ -1,45 +1,72 @@ # generated by datamodel-codegen: # filename: content_standards/get_media_buy_artifacts_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations -from ..core.version_envelope import AdcpVersionEnvelope +from typing import Literal, TypeAlias +from pydantic import AwareDatetime, ConfigDict -from typing import Any, TypeAlias +from ..core.version_envelope import AdcpVersionEnvelope +from . import artifact as artifact_1 +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core import pagination_response as pagination_response_1 -from pydantic import ConfigDict -from ..core import error as error_1 -from . import artifact as artifact_1 +class BrandContext(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + brand_id: str | None = None + sku_id: str | None = None -class ArtifactRecord(AdcpVersionEnvelope): +class Artifact(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') record_id: str - artifact: artifact_1.Artifact - timestamp: str | None = None + timestamp: AwareDatetime | None = None package_id: str | None = None + artifact: artifact_1.Artifact country: str | None = None channel: str | None = None - brand_context: dict[str, Any] | None = None - local_verdict: str | None = None + brand_context: BrandContext | None = None + local_verdict: Literal['pass', 'fail', 'unevaluated'] | None = None + + +class CollectionInfo(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + total_deliveries: int | None = None + total_collected: int | None = None + returned_count: int | None = None + effective_rate: float | None = None class GetMediaBuyArtifactsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') media_buy_id: str - artifacts: list[ArtifactRecord] - collection_info: dict[str, Any] | None = None - pagination: Any = None + artifacts: list[Artifact] + collection_info: CollectionInfo | None = None + pagination: pagination_response_1.PaginationResponse | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class GetMediaBuyArtifactsResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: list[error_1.Error] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +GetMediaBuyArtifactsResponse: TypeAlias = GetMediaBuyArtifactsResponse1 | GetMediaBuyArtifactsResponse2 -GetMediaBuyArtifactsResponse: TypeAlias = ( - GetMediaBuyArtifactsResponse1 | GetMediaBuyArtifactsResponse2 -) +__all__ = [ + 'GetMediaBuyArtifactsResponse', + 'GetMediaBuyArtifactsResponse1', + 'GetMediaBuyArtifactsResponse2', + 'Artifact', + 'BrandContext', + 'CollectionInfo', +] diff --git a/src/adcp/types/generated_poc/content_standards/validate_content_delivery_response.py b/src/adcp/types/generated_poc/content_standards/validate_content_delivery_response.py index 072f240f..61621120 100644 --- a/src/adcp/types/generated_poc/content_standards/validate_content_delivery_response.py +++ b/src/adcp/types/generated_poc/content_standards/validate_content_delivery_response.py @@ -1,30 +1,67 @@ # generated by datamodel-codegen: # filename: content_standards/validate_content_delivery_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations +from typing import Annotated, TypeAlias + +from pydantic import ConfigDict, Field + from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..enums import binary_verdict as binary_verdict_1 +from ..enums import feature_check_status as feature_check_status_1 -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias +class Summary(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + total_records: int + passed_records: int + failed_records: int -from pydantic import ConfigDict -from ..core import error as error_1 +class Feature(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + feature_id: str + status: feature_check_status_1.FeatureCheckStatus + policy_id: str | None = None + explanation: str | None = None + confidence: Annotated[float, Field(ge=0, le=1)] | None = None + + +class Result(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + record_id: str + verdict: binary_verdict_1.BinaryVerdict + features: list[Feature] | None = None class ValidateContentDeliveryResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - status: Any = None + summary: Summary + results: list[Result] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class ValidateContentDeliveryResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: list[error_1.Error] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None ValidateContentDeliveryResponse: TypeAlias = ValidateContentDeliveryResponse1 | ValidateContentDeliveryResponse2 + + +__all__ = [ + 'ValidateContentDeliveryResponse', + 'ValidateContentDeliveryResponse1', + 'ValidateContentDeliveryResponse2', + 'Feature', + 'Result', + 'Summary', +] diff --git a/src/adcp/types/generated_poc/creative/get_creative_features_response.py b/src/adcp/types/generated_poc/creative/get_creative_features_response.py index 999d8872..1bfe0665 100644 --- a/src/adcp/types/generated_poc/creative/get_creative_features_response.py +++ b/src/adcp/types/generated_poc/creative/get_creative_features_response.py @@ -1,36 +1,47 @@ # generated by datamodel-codegen: # filename: creative/get_creative_features_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations -from ..core.version_envelope import AdcpVersionEnvelope - +from typing import Annotated, TypeAlias -from typing import TypeAlias - -from pydantic import ConfigDict +from pydantic import AnyUrl, ConfigDict, Field, StringConstraints +from ..core.version_envelope import AdcpVersionEnvelope +from . import audit_observation as audit_observation_1 +from . import creative_feature_result as creative_feature_result_1 +from ..core import context as context_1 from ..core import creative_consumption as creative_consumption_1 from ..core import error as error_1 -from . import creative_feature_result as creative_feature_result_1 +from ..core import ext as ext_1 class GetCreativeFeaturesResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') results: list[creative_feature_result_1.CreativeFeatureResult] - detail_url: str | None = None + detail_url: AnyUrl | None = None + audit_observations: list[audit_observation_1.CreativeAuditObservation] | None = None pricing_option_id: str | None = None - vendor_cost: float | None = None - currency: str | None = None + vendor_cost: Annotated[float, Field(ge=0)] | None = None + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None consumption: creative_consumption_1.CreativeConsumption | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class GetCreativeFeaturesResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: list[error_1.Error] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +GetCreativeFeaturesResponse: TypeAlias = GetCreativeFeaturesResponse1 | GetCreativeFeaturesResponse2 -GetCreativeFeaturesResponse: TypeAlias = ( - GetCreativeFeaturesResponse1 | GetCreativeFeaturesResponse2 -) +__all__ = [ + 'GetCreativeFeaturesResponse', + 'GetCreativeFeaturesResponse1', + 'GetCreativeFeaturesResponse2', +] diff --git a/src/adcp/types/generated_poc/creative/preview_creative_response.py b/src/adcp/types/generated_poc/creative/preview_creative_response.py index 0ad619e0..72e855b8 100644 --- a/src/adcp/types/generated_poc/creative/preview_creative_response.py +++ b/src/adcp/types/generated_poc/creative/preview_creative_response.py @@ -1,56 +1,113 @@ # generated by datamodel-codegen: # filename: creative/preview_creative_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations +from typing import Annotated, Literal, TypeAlias + +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field + from ..core.version_envelope import AdcpVersionEnvelope +from . import preview_render as preview_render_1 +from ..core import context as context_1 +from ..core import creative_manifest as creative_manifest_1 +from ..core import error as error_1 +from ..core import ext as ext_1 + +class Input(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + name: str + macros: dict[str, str] | None = None + context_description: str | None = None -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias -from pydantic import ConfigDict +class Preview(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + preview_id: str + renders: Annotated[list[preview_render_1.PreviewRender], Field(min_length=1)] + input: Input -from ..core import error as error_1 -from . import preview_render as preview_render_1 +class Input2(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + name: str + macros: dict[str, str] | None = None + context_description: str | None = None -class PreviewInput(AdcpVersionEnvelope): +class Preview2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - name: str | None = None + preview_id: str + renders: Annotated[list[preview_render_1.PreviewRender], Field(min_length=1)] + input: Input2 -class Preview(AdcpVersionEnvelope): +class Response(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + previews: Annotated[list[Preview2], Field(min_length=1)] + interactive_url: AnyUrl | None = None + expires_at: AwareDatetime | None = None + + +class Result(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - renders: list[preview_render_1.PreviewRender] - preview_id: str | None = None - input: PreviewInput | None = None + success: bool + creative_id: str + response: Response | None = None + errors: Annotated[list[error_1.Error], Field(min_length=1)] | None = None + + +class Preview3(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + preview_id: str + renders: Annotated[list[preview_render_1.PreviewRender], Field(min_length=1)] class PreviewCreativeResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - response_type: Literal['single'] | None = None - previews: list[Preview] - expires_at: Any = None + response_type: Literal['single'] = 'single' + previews: Annotated[list[Preview], Field(min_length=1)] + interactive_url: AnyUrl | None = None + expires_at: AwareDatetime | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class PreviewCreativeResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - response_type: Literal['batch'] | None = None - results: list[Any] + response_type: Literal['batch'] = 'batch' + results: Annotated[list[Result], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class PreviewCreativeResponse3(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - response_type: Literal['variant'] | None = None - variant_id: str | None = None - rendered: list[Any] | None = None - - -PreviewCreativeResponse: TypeAlias = ( - PreviewCreativeResponse1 | PreviewCreativeResponse2 | PreviewCreativeResponse3 -) + response_type: Literal['variant'] = 'variant' + variant_id: str + creative_id: str | None = None + previews: Annotated[list[Preview3], Field(min_length=1)] + manifest: creative_manifest_1.CreativeManifest | None = None + expires_at: AwareDatetime | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +PreviewCreativeResponse: TypeAlias = PreviewCreativeResponse1 | PreviewCreativeResponse2 | PreviewCreativeResponse3 + + +__all__ = [ + 'PreviewCreativeResponse', + 'PreviewCreativeResponse1', + 'PreviewCreativeResponse2', + 'PreviewCreativeResponse3', + 'Input', + 'Input2', + 'Preview', + 'Preview2', + 'Preview3', + 'Response', + 'Result', +] diff --git a/src/adcp/types/generated_poc/creative/sync_creatives_response.py b/src/adcp/types/generated_poc/creative/sync_creatives_response.py index 805e47bb..2c9013ec 100644 --- a/src/adcp/types/generated_poc/creative/sync_creatives_response.py +++ b/src/adcp/types/generated_poc/creative/sync_creatives_response.py @@ -1,40 +1,73 @@ # generated by datamodel-codegen: # filename: creative/sync_creatives_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations -from ..core.version_envelope import AdcpVersionEnvelope - - -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias +from typing import Annotated, Literal, TypeAlias -from pydantic import ConfigDict +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StringConstraints +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import account as account_1 +from ..core import context as context_1 from ..core import error as error_1 - -from ..enums import creative_action +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import creative_action as creative_action_1 +from ..enums import creative_status as creative_status_1 +from ..enums import task_status as task_status_1 class Creative(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') creative_id: str - action: creative_action.CreativeAction | str + account: account_1.Account | None = None + action: creative_action_1.CreativeAction + status: creative_status_1.CreativeStatus | None = None + platform_id: str | None = None + changes: list[str] | None = None errors: list[error_1.Error] | None = None + warnings: list[str] | None = None + preview_url: AnyUrl | None = None + expires_at: AwareDatetime | None = None + assigned_to: list[str] | None = None + assignment_errors: dict[Annotated[str, StringConstraints(pattern='^[a-zA-Z0-9_-]+$')], str] | None = None class SyncCreativesResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') + dry_run: bool | None = None creatives: list[Creative] - status: Any = None + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class SyncCreativesResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class SyncCreativesResponse3(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow', validate_default=True) + status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted + task_id: str + message: Annotated[str, StringConstraints(max_length=2000)] | None = None + errors: list[error_1.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +SyncCreativesResponse: TypeAlias = SyncCreativesResponse1 | SyncCreativesResponse2 | SyncCreativesResponse3 -SyncCreativesResponse: TypeAlias = SyncCreativesResponse1 | SyncCreativesResponse2 +__all__ = [ + 'SyncCreativesResponse', + 'SyncCreativesResponse1', + 'SyncCreativesResponse2', + 'SyncCreativesResponse3', + 'Creative', +] diff --git a/src/adcp/types/generated_poc/media_buy/build_creative_response.py b/src/adcp/types/generated_poc/media_buy/build_creative_response.py index e6147839..47785c25 100644 --- a/src/adcp/types/generated_poc/media_buy/build_creative_response.py +++ b/src/adcp/types/generated_poc/media_buy/build_creative_response.py @@ -1,30 +1,232 @@ # generated by datamodel-codegen: # filename: media_buy/build_creative_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StringConstraints + from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import creative_consumption as creative_consumption_1 +from ..core import creative_manifest as creative_manifest_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core import format_id as format_id_1 +from ..core import signal_targeting as signal_targeting_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..creative import creative_feature_result as creative_feature_result_1 +from ..creative import preview_render as preview_render_1 +from ..enums import creative_selection_strategy as creative_selection_strategy_1 +from ..enums import task_status as task_status_1 -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias +class Input(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + name: str + macros: dict[str, str] | None = None + context_description: str | None = None -from pydantic import ConfigDict -from ..core import error as error_1 +class Preview2(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + preview_id: str + renders: Annotated[list[preview_render_1.PreviewRender], Field(min_length=1)] + input: Input + + +class Preview(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + previews: Annotated[list[Preview2], Field(min_length=1)] + interactive_url: AnyUrl | None = None + expires_at: AwareDatetime + + +class Input2(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + name: str + macros: dict[str, str] | None = None + context_description: str | None = None + + +class Preview4(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + preview_id: str + format_id: format_id_1.FormatReferenceStructuredObject + renders: Annotated[list[preview_render_1.PreviewRender], Field(min_length=1)] + input: Input2 + + +class Preview3(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + previews: Annotated[list[Preview4], Field(min_length=1)] + interactive_url: AnyUrl | None = None + expires_at: AwareDatetime + + +class CatalogItemRef(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + catalog_type: str | None = None + item_id: str + + +class Eval(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + features: list[creative_feature_result_1.CreativeFeatureResult] | None = None + ranked_against: Annotated[int, Field(ge=1)] | None = None + calls_used: Annotated[int, Field(ge=0)] | None = None + seconds_used: Annotated[float, Field(ge=0)] | None = None + ext: ext_1.ExtensionObject | None = None + + +class Variant(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + build_variant_id: str + parent_build_variant_id: str | None = None + creative_manifest: creative_manifest_1.CreativeManifest + variant_axis_value: Any | None = None + recommended: bool | None = None + rank: Annotated[int, Field(ge=1)] | None = None + eval: Eval | None = None + pricing_option_id: str | None = None + vendor_cost: Annotated[float, Field(ge=0)] | None = None + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None + consumption: creative_consumption_1.CreativeConsumption | None = None + + +class Creative(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + build_creative_id: str | None = None + catalog_item_ref: CatalogItemRef | None = None + signal_condition: signal_targeting_1.SignalTargeting | None = None + variants: Annotated[list[Variant], Field(min_length=1)] | None = None + errors: Annotated[list[error_1.Error], Field(min_length=1)] | None = None + + +class PerLeaf(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + catalog_item_ref: dict[str, Any] | None = None + variant_axis_value: Any | None = None + pricing_option_id: str | None = None + cost_low: Annotated[float, Field(ge=0)] | None = None + cost_high: Annotated[float, Field(ge=0)] | None = None + consumption_estimate: creative_consumption_1.CreativeConsumption | None = None + + +class Estimate(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + items_total: Annotated[int, Field(ge=0)] | None = None + items_to_produce: Annotated[int, Field(ge=0)] | None = None + conditions_total: Annotated[int, Field(ge=1)] | None = None + variants_per_item: Annotated[int, Field(ge=1)] | None = None + leaves_total: Annotated[int, Field(ge=0)] | None = None + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] + cost_low: Annotated[float, Field(ge=0)] + cost_high: Annotated[float, Field(ge=0)] + cost_expected: Annotated[float, Field(ge=0)] | None = None + basis: Literal['fixed', 'estimated_units', 'cpm_deferred'] + per_leaf: list[PerLeaf] | None = None class BuildCreativeResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - status: Any = None + creative_manifest: creative_manifest_1.CreativeManifest + build_variant_id: str | None = None + sandbox: bool | None = None + expires_at: AwareDatetime | None = None + preview: Preview | None = None + preview_error: error_1.Error | None = None + pricing_option_id: str | None = None + vendor_cost: Annotated[float, Field(ge=0)] | None = None + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None + consumption: creative_consumption_1.CreativeConsumption | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class BuildCreativeResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class BuildCreativeResponse3(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + creative_manifests: Annotated[list[creative_manifest_1.CreativeManifest], Field(min_length=1)] + sandbox: bool | None = None + expires_at: AwareDatetime | None = None + preview: Preview3 | None = None + preview_error: error_1.Error | None = None + pricing_option_id: str | None = None + vendor_cost: Annotated[float, Field(ge=0)] | None = None + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None + consumption: creative_consumption_1.CreativeConsumption | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class BuildCreativeResponse4(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + creatives: Annotated[list[Creative], Field(min_length=1)] + items_total: Annotated[int, Field(ge=0)] | None = None + items_returned: Annotated[int, Field(ge=0)] | None = None + leaves_total: Annotated[int, Field(ge=0)] | None = None + leaves_returned: Annotated[int, Field(ge=0)] | None = None + vendor_cost: Annotated[float, Field(ge=0)] | None = None + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None + keep_mode_applied: Literal['keep_all', 'keep_one', 'keep_some'] | None = None + selection_strategy_applied: creative_selection_strategy_1.CreativeSelectionStrategy | None = None + budget_status: Literal['complete', 'capped'] | None = None + errors: list[error_1.Error] | None = None + sandbox: bool | None = None + expires_at: AwareDatetime | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class BuildCreativeResponse5(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + mode: Literal['estimate'] = 'estimate' + estimate: Estimate + expires_at: AwareDatetime | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class BuildCreativeResponse6(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow', validate_default=True) + status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted + task_id: str + message: Annotated[str, StringConstraints(max_length=2000)] | None = None + errors: list[error_1.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +BuildCreativeResponse: TypeAlias = BuildCreativeResponse1 | BuildCreativeResponse2 | BuildCreativeResponse3 | BuildCreativeResponse4 | BuildCreativeResponse5 | BuildCreativeResponse6 -BuildCreativeResponse: TypeAlias = BuildCreativeResponse1 | BuildCreativeResponse2 +__all__ = [ + 'BuildCreativeResponse', + 'BuildCreativeResponse1', + 'BuildCreativeResponse2', + 'BuildCreativeResponse3', + 'BuildCreativeResponse4', + 'BuildCreativeResponse5', + 'BuildCreativeResponse6', + 'CatalogItemRef', + 'Creative', + 'Estimate', + 'Eval', + 'Input', + 'Input2', + 'PerLeaf', + 'Preview', + 'Preview2', + 'Preview3', + 'Preview4', + 'Variant', +] diff --git a/src/adcp/types/generated_poc/media_buy/create_media_buy_response.py b/src/adcp/types/generated_poc/media_buy/create_media_buy_response.py index 8c3fa836..ae3cd0a9 100644 --- a/src/adcp/types/generated_poc/media_buy/create_media_buy_response.py +++ b/src/adcp/types/generated_poc/media_buy/create_media_buy_response.py @@ -1,89 +1,95 @@ # generated by datamodel-codegen: # filename: media_buy/create_media_buy_response.json -# timestamp: 2026-05-28T10:34:10+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations -from ..core.version_envelope import AdcpVersionEnvelope - - -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias - -from pydantic import AwareDatetime, ConfigDict, model_validator +from typing import Annotated, Any, Literal, TypeAlias -from adcp.types.media_buy_status_helpers import MEDIA_BUY_LEGACY_STATUS_VALUES, unwrap_enum_value +from pydantic import AwareDatetime, ConfigDict, Field, StringConstraints, model_validator +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import account as account_1 +from ..core import business_entity as business_entity_1 +from ..core import context as context_1 from ..core import error as error_1 from ..core import ext as ext_1 +from ..core import media_buy_available_action as media_buy_available_action_1 from ..core import package as package_1 +from ..core import planned_delivery as planned_delivery_1 from ..core.protocol_envelope import ProtocolEnvelope from ..enums import media_buy_status as media_buy_status_1 +from ..enums import media_buy_valid_action as media_buy_valid_action_1 from ..enums import task_status as task_status_1 +from adcp.types.media_buy_status_helpers import MEDIA_BUY_LEGACY_STATUS_VALUES, unwrap_enum_value + class CreateMediaBuyResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') media_buy_id: str - packages: list[package_1.Package] - buyer_ref: str | None = None - confirmed_at: AwareDatetime | None - revision: int + account: account_1.Account | None = None + invoice_recipient: business_entity_1.BusinessEntity | None = None media_buy_status: media_buy_status_1.MediaBuyStatus | None = None - status: Literal["completed"] + status: Literal['completed'] + confirmed_at: AwareDatetime + creative_deadline: AwareDatetime | None = None + revision: Annotated[int, Field(ge=1)] + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None + total_budget: Annotated[float, Field(ge=0)] | None = None + valid_actions: list[media_buy_valid_action_1.MediaBuyValidAction] | None = None + available_actions: list[media_buy_available_action_1.MediaBuyAvailableAction] | None = None + packages: list[package_1.Package] + planned_delivery: planned_delivery_1.PlannedDelivery | None = None + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None @model_validator(mode='before') @classmethod def _normalize_legacy_status(cls, data: Any) -> Any: if not isinstance(data, dict): return data - raw_status = unwrap_enum_value(data.get("status")) - media_buy_status = unwrap_enum_value(data.get("media_buy_status")) + raw_status = unwrap_enum_value(data.get('status')) + media_buy_status = unwrap_enum_value(data.get('media_buy_status')) if raw_status is None: data = dict(data) - data["status"] = "completed" - elif raw_status == "completed": + data['status'] = 'completed' + elif raw_status == 'completed': data = dict(data) - data["status"] = "completed" + data['status'] = 'completed' elif media_buy_status is None and raw_status in MEDIA_BUY_LEGACY_STATUS_VALUES: data = dict(data) - data["media_buy_status"] = raw_status - data["status"] = "completed" + data['media_buy_status'] = raw_status + data['status'] = 'completed' elif media_buy_status is not None and raw_status == media_buy_status: data = dict(data) - data["status"] = "completed" + data['status'] = 'completed' return data class CreateMediaBuyResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class CreateMediaBuyResponse3(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow', use_enum_values=True, validate_default=True) + model_config = ConfigDict(extra='allow', validate_default=True) status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted task_id: str + message: Annotated[str, StringConstraints(max_length=2000)] | None = None errors: list[error_1.Error] | None = None + context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = None - @model_validator(mode='before') - @classmethod - def _normalize_submitted_status(cls, data: Any) -> Any: - if isinstance(data, dict) and data.get("status") == "submitted": - data = dict(data) - data["status"] = task_status_1.TaskStatus.submitted - return data +CreateMediaBuyResponse: TypeAlias = CreateMediaBuyResponse1 | CreateMediaBuyResponse2 | CreateMediaBuyResponse3 -CreateMediaBuyResponse: TypeAlias = ( - CreateMediaBuyResponse1 | CreateMediaBuyResponse2 | CreateMediaBuyResponse3 -) __all__ = [ - "CreateMediaBuyResponse", - "CreateMediaBuyResponse1", - "CreateMediaBuyResponse2", - "CreateMediaBuyResponse3", + 'CreateMediaBuyResponse', + 'CreateMediaBuyResponse1', + 'CreateMediaBuyResponse2', + 'CreateMediaBuyResponse3', ] diff --git a/src/adcp/types/generated_poc/media_buy/log_event_response.py b/src/adcp/types/generated_poc/media_buy/log_event_response.py index 18cb3540..e2119072 100644 --- a/src/adcp/types/generated_poc/media_buy/log_event_response.py +++ b/src/adcp/types/generated_poc/media_buy/log_event_response.py @@ -1,30 +1,51 @@ # generated by datamodel-codegen: # filename: media_buy/log_event_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations -from ..core.version_envelope import AdcpVersionEnvelope +from typing import Annotated, TypeAlias +from pydantic import ConfigDict, Field -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 -from pydantic import ConfigDict -from ..core import error as error_1 +class PartialFailure(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + event_id: str + code: str + message: str class LogEventResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - status: Any = None + events_received: Annotated[int, Field(ge=0)] + events_processed: Annotated[int, Field(ge=0)] + partial_failures: list[PartialFailure] | None = None + warnings: list[str] | None = None + match_quality: Annotated[float, Field(ge=0, le=1)] | None = None + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class LogEventResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None LogEventResponse: TypeAlias = LogEventResponse1 | LogEventResponse2 + + +__all__ = [ + 'LogEventResponse', + 'LogEventResponse1', + 'LogEventResponse2', + 'PartialFailure', +] diff --git a/src/adcp/types/generated_poc/media_buy/provide_performance_feedback_response.py b/src/adcp/types/generated_poc/media_buy/provide_performance_feedback_response.py index 84b53d11..3e2f2db2 100644 --- a/src/adcp/types/generated_poc/media_buy/provide_performance_feedback_response.py +++ b/src/adcp/types/generated_poc/media_buy/provide_performance_feedback_response.py @@ -1,30 +1,39 @@ # generated by datamodel-codegen: # filename: media_buy/provide_performance_feedback_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations -from ..core.version_envelope import AdcpVersionEnvelope - - -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias +from typing import Annotated, Literal, TypeAlias -from pydantic import ConfigDict +from pydantic import ConfigDict, Field +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 from ..core import error as error_1 +from ..core import ext as ext_1 class ProvidePerformanceFeedbackResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - status: Any = None + success: Literal[True] + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class ProvidePerformanceFeedbackResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None ProvidePerformanceFeedbackResponse: TypeAlias = ProvidePerformanceFeedbackResponse1 | ProvidePerformanceFeedbackResponse2 + + +__all__ = [ + 'ProvidePerformanceFeedbackResponse', + 'ProvidePerformanceFeedbackResponse1', + 'ProvidePerformanceFeedbackResponse2', +] diff --git a/src/adcp/types/generated_poc/media_buy/sync_audiences_response.py b/src/adcp/types/generated_poc/media_buy/sync_audiences_response.py index cbc05b7a..0d89cc88 100644 --- a/src/adcp/types/generated_poc/media_buy/sync_audiences_response.py +++ b/src/adcp/types/generated_poc/media_buy/sync_audiences_response.py @@ -1,30 +1,81 @@ # generated by datamodel-codegen: # filename: media_buy/sync_audiences_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations +from typing import Annotated, Literal, TypeAlias + +from pydantic import AwareDatetime, ConfigDict, Field, StringConstraints + from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import audience_status as audience_status_1 +from ..enums import match_id_type as match_id_type_1 +from ..enums import task_status as task_status_1 -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias +class MatchBreakdown(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + id_type: match_id_type_1.MatchIdType + submitted: Annotated[int, Field(ge=0)] + matched: Annotated[int, Field(ge=0)] + match_rate: Annotated[float, Field(ge=0, le=1)] -from pydantic import ConfigDict -from ..core import error as error_1 +class Audience(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + audience_id: str + name: str | None = None + seller_id: str | None = None + action: Literal['created', 'updated', 'unchanged', 'deleted', 'failed'] + status: audience_status_1.AudienceStatus | None = None + uploaded_count: Annotated[int, Field(ge=0)] | None = None + total_uploaded_count: Annotated[int, Field(ge=0)] | None = None + matched_count: Annotated[int, Field(ge=0)] | None = None + effective_match_rate: Annotated[float, Field(ge=0, le=1)] | None = None + match_breakdown: Annotated[list[MatchBreakdown], Field(min_length=1)] | None = None + last_synced_at: AwareDatetime | None = None + minimum_size: Annotated[int, Field(ge=1)] | None = None + errors: list[error_1.Error] | None = None class SyncAudiencesResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - status: Any = None + audiences: list[Audience] + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class SyncAudiencesResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class SyncAudiencesResponse3(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow', validate_default=True) + status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted + task_id: str + message: Annotated[str, StringConstraints(max_length=2000)] | None = None + errors: list[error_1.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +SyncAudiencesResponse: TypeAlias = SyncAudiencesResponse1 | SyncAudiencesResponse2 | SyncAudiencesResponse3 -SyncAudiencesResponse: TypeAlias = SyncAudiencesResponse1 | SyncAudiencesResponse2 +__all__ = [ + 'SyncAudiencesResponse', + 'SyncAudiencesResponse1', + 'SyncAudiencesResponse2', + 'SyncAudiencesResponse3', + 'Audience', + 'MatchBreakdown', +] diff --git a/src/adcp/types/generated_poc/media_buy/sync_catalogs_response.py b/src/adcp/types/generated_poc/media_buy/sync_catalogs_response.py index 9817b004..b90c22c0 100644 --- a/src/adcp/types/generated_poc/media_buy/sync_catalogs_response.py +++ b/src/adcp/types/generated_poc/media_buy/sync_catalogs_response.py @@ -1,42 +1,81 @@ # generated by datamodel-codegen: # filename: media_buy/sync_catalogs_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations -from ..core.version_envelope import AdcpVersionEnvelope - - -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias +from typing import Annotated, Literal, TypeAlias -from pydantic import ConfigDict +from pydantic import AwareDatetime, ConfigDict, Field, StringConstraints +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import catalog_action as catalog_action_1 +from ..enums import catalog_item_status as catalog_item_status_1 +from ..enums import task_status as task_status_1 + -from ..enums import catalog_action +class ItemIssue(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + item_id: str + status: catalog_item_status_1.CatalogItemStatus + reasons: list[str] | None = None class Catalog(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') catalog_id: str - action: catalog_action.CatalogAction - item_count: int | None = None - items_pending: int | None = None + action: catalog_action_1.CatalogAction + platform_id: str | None = None + item_count: Annotated[int, Field(ge=0)] | None = None + items_approved: Annotated[int, Field(ge=0)] | None = None + items_pending: Annotated[int, Field(ge=0)] | None = None + items_rejected: Annotated[int, Field(ge=0)] | None = None + item_issues: list[ItemIssue] | None = None + last_synced_at: AwareDatetime | None = None + next_fetch_at: AwareDatetime | None = None + changes: list[str] | None = None errors: list[error_1.Error] | None = None + warnings: list[str] | None = None class SyncCatalogsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') + dry_run: bool | None = None catalogs: list[Catalog] - status: Any = None + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class SyncCatalogsResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class SyncCatalogsResponse3(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow', validate_default=True) + status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted + task_id: str + message: Annotated[str, StringConstraints(max_length=2000)] | None = None + errors: list[error_1.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +SyncCatalogsResponse: TypeAlias = SyncCatalogsResponse1 | SyncCatalogsResponse2 | SyncCatalogsResponse3 -SyncCatalogsResponse: TypeAlias = SyncCatalogsResponse1 | SyncCatalogsResponse2 +__all__ = [ + 'SyncCatalogsResponse', + 'SyncCatalogsResponse1', + 'SyncCatalogsResponse2', + 'SyncCatalogsResponse3', + 'Catalog', + 'ItemIssue', +] diff --git a/src/adcp/types/generated_poc/media_buy/sync_event_sources_response.py b/src/adcp/types/generated_poc/media_buy/sync_event_sources_response.py index 794ae307..eead94cd 100644 --- a/src/adcp/types/generated_poc/media_buy/sync_event_sources_response.py +++ b/src/adcp/types/generated_poc/media_buy/sync_event_sources_response.py @@ -1,30 +1,68 @@ # generated by datamodel-codegen: # filename: media_buy/sync_event_sources_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations +from typing import Annotated, Literal, TypeAlias + +from pydantic import ConfigDict, Field + from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import event_source_health as event_source_health_1 +from ..core import event_surface as event_surface_1 +from ..core import ext as ext_1 +from ..enums import action_source as action_source_1 +from ..enums import event_type as event_type_1 -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Any, Literal, TypeAlias +class Setup(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + snippet: str | None = None + snippet_type: Literal['javascript', 'html', 'pixel_url', 'server_only'] | None = None + instructions: str | None = None -from pydantic import ConfigDict -from ..core import error as error_1 +class EventSource(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + event_source_id: str + name: str | None = None + seller_id: str | None = None + event_types: list[event_type_1.EventType] | None = None + action_source: action_source_1.ActionSource | None = None + surface: event_surface_1.EventSurface | None = None + managed_by: Literal['buyer', 'seller'] | None = None + setup: Setup | None = None + action: Literal['created', 'updated', 'unchanged', 'deleted', 'failed'] + health: event_source_health_1.EventSourceHealth | None = None + errors: list[error_1.Error] | None = None + ext: ext_1.ExtensionObject | None = None class SyncEventSourcesResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - status: Any = None + event_sources: list[EventSource] + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class SyncEventSourcesResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None SyncEventSourcesResponse: TypeAlias = SyncEventSourcesResponse1 | SyncEventSourcesResponse2 + + +__all__ = [ + 'SyncEventSourcesResponse', + 'SyncEventSourcesResponse1', + 'SyncEventSourcesResponse2', + 'EventSource', + 'Setup', +] diff --git a/src/adcp/types/generated_poc/media_buy/update_media_buy_response.py b/src/adcp/types/generated_poc/media_buy/update_media_buy_response.py index 8c5c3da3..930b1662 100644 --- a/src/adcp/types/generated_poc/media_buy/update_media_buy_response.py +++ b/src/adcp/types/generated_poc/media_buy/update_media_buy_response.py @@ -1,91 +1,92 @@ # generated by datamodel-codegen: # filename: media_buy/update_media_buy_response.json -# timestamp: 2026-05-29T23:56:01+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations -from ..core.version_envelope import AdcpVersionEnvelope - - -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. from collections.abc import Sequence -from typing import Any, Literal, TypeAlias -from pydantic import AwareDatetime, ConfigDict, model_validator +from typing import Annotated, Any, Literal, TypeAlias -from adcp.types.media_buy_status_helpers import MEDIA_BUY_LEGACY_STATUS_VALUES, unwrap_enum_value +from pydantic import AwareDatetime, ConfigDict, Field, StringConstraints, model_validator +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import business_entity as business_entity_1 +from ..core import context as context_1 from ..core import error as error_1 from ..core import ext as ext_1 +from ..core import media_buy_available_action as media_buy_available_action_1 from ..core import package as package_1 from ..core.protocol_envelope import ProtocolEnvelope from ..enums import media_buy_status as media_buy_status_1 +from ..enums import media_buy_valid_action as media_buy_valid_action_1 from ..enums import task_status as task_status_1 +from adcp.types.media_buy_status_helpers import MEDIA_BUY_LEGACY_STATUS_VALUES, unwrap_enum_value class UpdateMediaBuyResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') media_buy_id: str - affected_packages: Sequence[package_1.Package] | None = None - packages: list[package_1.Package] | None = None - buyer_ref: str | None = None - revision: int media_buy_status: media_buy_status_1.MediaBuyStatus | None = None - status: Literal["completed"] + status: Literal['completed'] + revision: Annotated[int, Field(ge=1)] + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None + total_budget: Annotated[float, Field(ge=0)] | None = None + implementation_date: AwareDatetime | None = None + invoice_recipient: business_entity_1.BusinessEntity | None = None + affected_packages: Sequence[package_1.Package] | None = None + valid_actions: list[media_buy_valid_action_1.MediaBuyValidAction] | None = None + available_actions: list[media_buy_available_action_1.MediaBuyAvailableAction] | None = None + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None @model_validator(mode='before') @classmethod def _normalize_legacy_status(cls, data: Any) -> Any: if not isinstance(data, dict): return data - raw_status = unwrap_enum_value(data.get("status")) - media_buy_status = unwrap_enum_value(data.get("media_buy_status")) + raw_status = unwrap_enum_value(data.get('status')) + media_buy_status = unwrap_enum_value(data.get('media_buy_status')) if raw_status is None: data = dict(data) - data["status"] = "completed" - elif raw_status == "completed": + data['status'] = 'completed' + elif raw_status == 'completed': data = dict(data) - data["status"] = "completed" + data['status'] = 'completed' elif media_buy_status is None and raw_status in MEDIA_BUY_LEGACY_STATUS_VALUES: data = dict(data) - data["media_buy_status"] = raw_status - data["status"] = "completed" + data['media_buy_status'] = raw_status + data['status'] = 'completed' elif media_buy_status is not None and raw_status == media_buy_status: data = dict(data) - data["status"] = "completed" + data['status'] = 'completed' return data class UpdateMediaBuyResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class UpdateMediaBuyResponse3(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow', use_enum_values=True, validate_default=True) + model_config = ConfigDict(extra='allow', validate_default=True) status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted task_id: str + message: Annotated[str, StringConstraints(max_length=2000)] | None = None errors: list[error_1.Error] | None = None + context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = None - @model_validator(mode='before') - @classmethod - def _normalize_submitted_status(cls, data: Any) -> Any: - if isinstance(data, dict) and data.get("status") == "submitted": - data = dict(data) - data["status"] = task_status_1.TaskStatus.submitted - return data +UpdateMediaBuyResponse: TypeAlias = UpdateMediaBuyResponse1 | UpdateMediaBuyResponse2 | UpdateMediaBuyResponse3 -UpdateMediaBuyResponse: TypeAlias = ( - UpdateMediaBuyResponse1 | UpdateMediaBuyResponse2 | UpdateMediaBuyResponse3 -) __all__ = [ - "UpdateMediaBuyResponse", - "UpdateMediaBuyResponse1", - "UpdateMediaBuyResponse2", - "UpdateMediaBuyResponse3", + 'UpdateMediaBuyResponse', + 'UpdateMediaBuyResponse1', + 'UpdateMediaBuyResponse2', + 'UpdateMediaBuyResponse3', ] diff --git a/src/adcp/types/generated_poc/signals/activate_signal_response.py b/src/adcp/types/generated_poc/signals/activate_signal_response.py index c9e9a5fe..8c6ee7ab 100644 --- a/src/adcp/types/generated_poc/signals/activate_signal_response.py +++ b/src/adcp/types/generated_poc/signals/activate_signal_response.py @@ -1,32 +1,40 @@ # generated by datamodel-codegen: # filename: signals/activate_signal_response.json -# timestamp: 2026-05-24T21:27:05+00:00 +# timestamp: preserved-by-post-generate-fixes from __future__ import annotations -from ..core.version_envelope import AdcpVersionEnvelope - - -# Backward-compatible SDK response arms. Upstream beta 3 schemas collapse this -# task response to the common protocol envelope, but the Python SDK keeps the -# historical numbered variants as ergonomic construction/parsing aliases. -from typing import Literal, TypeAlias +from typing import Annotated, TypeAlias -from pydantic import ConfigDict +from pydantic import ConfigDict, Field +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 from ..core import deployment as deployment_1 from ..core import error as error_1 +from ..core import ext as ext_1 class ActivateSignalResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') deployments: list[deployment_1.Deployment] sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None class ActivateSignalResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None ActivateSignalResponse: TypeAlias = ActivateSignalResponse1 | ActivateSignalResponse2 + + +__all__ = [ + 'ActivateSignalResponse', + 'ActivateSignalResponse1', + 'ActivateSignalResponse2', +] diff --git a/src/adcp/types/guards.py b/src/adcp/types/guards.py index cd87ded0..35f5db10 100644 --- a/src/adcp/types/guards.py +++ b/src/adcp/types/guards.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import Any, TypeGuard +from typing import Any, TypeAlias, TypeGuard def is_adcp_error(response: Any) -> bool: @@ -75,6 +75,10 @@ def is_adcp_success(response: Any) -> bool: ActivateSignalErrorResponse, ActivateSignalSuccessResponse, BuildCreativeErrorResponse, + BuildCreativeResponse3, + BuildCreativeResponse4, + BuildCreativeResponse5, + BuildCreativeSubmittedResponse, BuildCreativeSuccessResponse, CalibrateContentErrorResponse, CalibrateContentSuccessResponse, @@ -92,8 +96,10 @@ def is_adcp_success(response: Any) -> bool: SyncAccountsErrorResponse, SyncAccountsSuccessResponse, SyncCatalogsErrorResponse, + SyncCatalogsSubmittedResponse, SyncCatalogsSuccessResponse, SyncCreativesErrorResponse, + SyncCreativesSubmittedResponse, SyncCreativesSuccessResponse, SyncEventSourcesErrorResponse, SyncEventSourcesSuccessResponse, @@ -105,19 +111,33 @@ def is_adcp_success(response: Any) -> bool: ) # Type aliases for response unions -CreateMediaBuyResponse = ( +CreateMediaBuyResponse: TypeAlias = ( CreateMediaBuySuccessResponse | CreateMediaBuyErrorResponse | CreateMediaBuySubmittedResponse ) -UpdateMediaBuyResponse = ( +UpdateMediaBuyResponse: TypeAlias = ( UpdateMediaBuySuccessResponse | UpdateMediaBuyErrorResponse | UpdateMediaBuySubmittedResponse ) -ActivateSignalResponse = ActivateSignalSuccessResponse | ActivateSignalErrorResponse -BuildCreativeResponse = BuildCreativeSuccessResponse | BuildCreativeErrorResponse -SyncCreativesResponse = SyncCreativesSuccessResponse | SyncCreativesErrorResponse -SyncAccountsResponse = SyncAccountsSuccessResponse | SyncAccountsErrorResponse -LogEventResponse = LogEventSuccessResponse | LogEventErrorResponse -SyncCatalogsResponse = SyncCatalogsSuccessResponse | SyncCatalogsErrorResponse -SyncEventSourcesResponse = SyncEventSourcesSuccessResponse | SyncEventSourcesErrorResponse +ActivateSignalResponse: TypeAlias = ActivateSignalSuccessResponse | ActivateSignalErrorResponse +BuildCreativeSuccessBranches: TypeAlias = ( + BuildCreativeSuccessResponse + | BuildCreativeResponse3 + | BuildCreativeResponse4 + | BuildCreativeResponse5 +) +BuildCreativeResponse: TypeAlias = ( + BuildCreativeSuccessBranches | BuildCreativeErrorResponse | BuildCreativeSubmittedResponse +) +SyncCreativesResponse: TypeAlias = ( + SyncCreativesSuccessResponse | SyncCreativesErrorResponse | SyncCreativesSubmittedResponse +) +SyncAccountsResponse: TypeAlias = SyncAccountsSuccessResponse | SyncAccountsErrorResponse +LogEventResponse: TypeAlias = LogEventSuccessResponse | LogEventErrorResponse +SyncCatalogsResponse: TypeAlias = ( + SyncCatalogsSuccessResponse | SyncCatalogsErrorResponse | SyncCatalogsSubmittedResponse +) +SyncEventSourcesResponse: TypeAlias = ( + SyncEventSourcesSuccessResponse | SyncEventSourcesErrorResponse +) # --- Create Media Buy --- @@ -223,10 +243,19 @@ def is_activate_signal_error( # --- Build Creative --- +def is_build_creative_submitted( + response: BuildCreativeResponse, +) -> TypeGuard[BuildCreativeSubmittedResponse]: + """Check if a BuildCreativeResponse is the async submitted envelope.""" + return getattr(response, "status", None) == "submitted" and hasattr(response, "task_id") + + def is_build_creative_success( response: BuildCreativeResponse, -) -> TypeGuard[BuildCreativeSuccessResponse]: - """Check if a BuildCreativeResponse is a success.""" +) -> TypeGuard[BuildCreativeSuccessBranches]: + """Check if a BuildCreativeResponse is a synchronous success.""" + if is_build_creative_submitted(response): + return False return not is_adcp_error(response) @@ -234,16 +263,27 @@ def is_build_creative_error( response: BuildCreativeResponse, ) -> TypeGuard[BuildCreativeErrorResponse]: """Check if a BuildCreativeResponse is an error.""" + if is_build_creative_submitted(response): + return False return is_adcp_error(response) # --- Sync Creatives --- +def is_sync_creatives_submitted( + response: SyncCreativesResponse, +) -> TypeGuard[SyncCreativesSubmittedResponse]: + """Check if a SyncCreativesResponse is the async submitted envelope.""" + return getattr(response, "status", None) == "submitted" and hasattr(response, "task_id") + + def is_sync_creatives_success( response: SyncCreativesResponse, ) -> TypeGuard[SyncCreativesSuccessResponse]: - """Check if a SyncCreativesResponse is a success.""" + """Check if a SyncCreativesResponse is a synchronous success.""" + if is_sync_creatives_submitted(response): + return False return not is_adcp_error(response) @@ -251,6 +291,8 @@ def is_sync_creatives_error( response: SyncCreativesResponse, ) -> TypeGuard[SyncCreativesErrorResponse]: """Check if a SyncCreativesResponse is an error.""" + if is_sync_creatives_submitted(response): + return False return is_adcp_error(response) @@ -308,10 +350,19 @@ def is_log_event_error( # --- Sync Catalogs --- +def is_sync_catalogs_submitted( + response: SyncCatalogsResponse, +) -> TypeGuard[SyncCatalogsSubmittedResponse]: + """Check if a SyncCatalogsResponse is the async submitted envelope.""" + return getattr(response, "status", None) == "submitted" and hasattr(response, "task_id") + + def is_sync_catalogs_success( response: SyncCatalogsResponse, ) -> TypeGuard[SyncCatalogsSuccessResponse]: - """Check if a SyncCatalogsResponse is a success.""" + """Check if a SyncCatalogsResponse is a synchronous success.""" + if is_sync_catalogs_submitted(response): + return False return not is_adcp_error(response) @@ -319,6 +370,8 @@ def is_sync_catalogs_error( response: SyncCatalogsResponse, ) -> TypeGuard[SyncCatalogsErrorResponse]: """Check if a SyncCatalogsResponse is an error.""" + if is_sync_catalogs_submitted(response): + return False return is_adcp_error(response) @@ -387,8 +440,10 @@ def is_get_creative_features_success( # Creative guards "is_build_creative_success", "is_build_creative_error", + "is_build_creative_submitted", "is_sync_creatives_success", "is_sync_creatives_error", + "is_sync_creatives_submitted", # Feedback guards "is_performance_feedback_success", "is_performance_feedback_error", @@ -403,6 +458,8 @@ def is_get_creative_features_success( # Catalog guards "is_sync_catalogs_success", "is_sync_catalogs_error", + "is_sync_catalogs_submitted", + "SyncEventSourcesResponse", # Content standards guards "is_calibrate_content_success", "is_validate_content_delivery_success", diff --git a/src/adcp/validation/schema_loader.py b/src/adcp/validation/schema_loader.py index eff625c9..5d994312 100644 --- a/src/adcp/validation/schema_loader.py +++ b/src/adcp/validation/schema_loader.py @@ -27,8 +27,10 @@ import json import logging +import re import threading import warnings +from datetime import datetime from importlib.resources import as_file, files from pathlib import Path from typing import Any, Literal @@ -48,6 +50,35 @@ Direction = Literal["request", "sync", "submitted", "working", "input-required"] +_RFC3339_DATE_TIME = re.compile( + r"^\d{4}-\d{2}-\d{2}[Tt]" + r"(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d" + r"(?:\.\d+)?" + r"(?:[Zz]|[+-](?:[01]\d|2[0-3]):[0-5]\d)$" +) + + +def _is_rfc3339_date_time(instance: Any) -> bool: + """Return whether ``instance`` is an RFC3339 date-time string. + + ``jsonschema`` treats unknown formats as annotations. Its optional + ``date-time`` checker is not always installed, but AdCP schemas use + ``format: date-time`` inside ``oneOf`` branches, so the format must + participate in validation for values like ``"asap"`` to select the + intended branch. + """ + if not isinstance(instance, str): + return True + if _RFC3339_DATE_TIME.fullmatch(instance) is None: + return False + normalized = instance[:-1] + "+00:00" if instance.endswith(("Z", "z")) else instance + try: + datetime.fromisoformat(normalized) + except ValueError: + return False + return True + + class _SchemaRoot: """Filesystem view of the schema tree, regardless of packaged vs dev.""" @@ -289,7 +320,7 @@ def get_validator( return None try: - from jsonschema import Draft7Validator + from jsonschema import Draft7Validator, FormatChecker from jsonschema.exceptions import SchemaError except ImportError as exc: # pragma: no cover raise RuntimeError( @@ -305,7 +336,13 @@ def get_validator( return cached try: resolver = _make_ref_resolver(state, file, schema) - validator = Draft7Validator(schema, resolver=resolver) + format_checker = FormatChecker() + format_checker.checks("date-time")(_is_rfc3339_date_time) + validator = Draft7Validator( + schema, + resolver=resolver, + format_checker=format_checker, + ) except SchemaError as exc: logger.warning("Invalid schema %s for %s: %s", file, key, exc) return None diff --git a/tests/fixtures/public_api_snapshot.json b/tests/fixtures/public_api_snapshot.json index 819a5aa5..53dec2f7 100644 --- a/tests/fixtures/public_api_snapshot.json +++ b/tests/fixtures/public_api_snapshot.json @@ -70,6 +70,7 @@ "BuildCreativeRequest", "BuildCreativeResponse", "BuildCreativeResponse1", + "BuildCreativeSubmittedResponse", "BuildCreativeSuccessResponse", "BuyingMode", "CREATIVE_AGENT_CONFIG", @@ -337,6 +338,7 @@ "SyncAudiencesRequest", "SyncAudiencesResponse", "SyncAudiencesResponse1", + "SyncAudiencesSubmittedResponse", "SyncAudiencesSuccessResponse", "SyncCatalogResult", "SyncCatalogsErrorResponse", @@ -345,6 +347,7 @@ "SyncCatalogsResponse", "SyncCatalogsResponse1", "SyncCatalogsSubmitted", + "SyncCatalogsSubmittedResponse", "SyncCatalogsSuccessResponse", "SyncCatalogsWorking", "SyncCreativeResult", @@ -352,6 +355,8 @@ "SyncCreativesRequest", "SyncCreativesResponse", "SyncCreativesResponse1", + "SyncCreativesResponse3", + "SyncCreativesSubmittedResponse", "SyncCreativesSuccessResponse", "SyncEventSourcesErrorResponse", "SyncEventSourcesRequest", @@ -549,6 +554,7 @@ "BuildCreativeRequest", "BuildCreativeResponse", "BuildCreativeResponse1", + "BuildCreativeSubmittedResponse", "BuildCreativeSuccessResponse", "BusinessEntity", "BusinessEntityResponse", @@ -1011,6 +1017,7 @@ "SyncAudiencesRequest", "SyncAudiencesResponse", "SyncAudiencesResponse1", + "SyncAudiencesSubmittedResponse", "SyncAudiencesSuccessResponse", "SyncCatalogResult", "SyncCatalogsErrorResponse", @@ -1019,6 +1026,7 @@ "SyncCatalogsResponse", "SyncCatalogsResponse1", "SyncCatalogsSubmitted", + "SyncCatalogsSubmittedResponse", "SyncCatalogsSuccessResponse", "SyncCatalogsWorking", "SyncCreativeResult", @@ -1026,6 +1034,8 @@ "SyncCreativesRequest", "SyncCreativesResponse", "SyncCreativesResponse1", + "SyncCreativesResponse3", + "SyncCreativesSubmittedResponse", "SyncCreativesSuccessResponse", "SyncEventSourcesErrorResponse", "SyncEventSourcesRequest", diff --git a/tests/test_code_generation.py b/tests/test_code_generation.py index be3e6d91..207bc97d 100644 --- a/tests/test_code_generation.py +++ b/tests/test_code_generation.py @@ -10,8 +10,8 @@ from __future__ import annotations -def test_protocol_envelope_import_restored_for_manual_response_arms(): - """Manual response arms that inherit ProtocolEnvelope must keep the import.""" +def test_protocol_envelope_import_restored_for_response_arms(): + """Response arms that inherit ProtocolEnvelope must keep the import.""" from scripts.post_generate_fixes import _sync_protocol_envelope_import source = ( @@ -111,6 +111,190 @@ def test_semantic_response_aliases_resolve_to_concrete_generated_arms(): assert alias is expected, f"{alias_name} no longer points at {module_name}.{class_name}" +def test_sync_creatives_response_arm_matches_schema_creative_fields(): + """sync_creatives response arm must expose every schema Creative field.""" + import json + from pathlib import Path + + import pytest + from pydantic import ValidationError + + from adcp._version import _read_packaged_version + from adcp.types.generated_poc.creative.sync_creatives_response import Creative + from adcp.validation.version import resolve_bundle_key + + bundle_key = resolve_bundle_key(_read_packaged_version()) + schema_path = ( + Path("schemas") / "cache" / bundle_key / "creative" / "sync-creatives-response.json" + ) + schema = json.loads(schema_path.read_text()) + creative_schema = schema["oneOf"][0]["properties"]["creatives"]["items"] + + assert set(Creative.model_fields) >= set(creative_schema["properties"]) + + for payload in [ + {"creative_id": "c1", "action": "updated", "status": "banana"}, + {"creative_id": "c1", "action": "banana"}, + {"creative_id": "c1", "action": "updated", "preview_url": "not a url"}, + {"creative_id": "c1", "action": "updated", "expires_at": "not a datetime"}, + { + "creative_id": "c1", + "action": "updated", + "assignment_errors": {"bad.key": "not a package id"}, + }, + ]: + with pytest.raises(ValidationError): + Creative.model_validate(payload) + + +def test_sync_creatives_response_arm_accepts_submitted_response(): + """sync_creatives schema includes a submitted async response branch.""" + from pydantic import TypeAdapter + + from adcp.types.generated_poc.creative.sync_creatives_response import ( + SyncCreativesResponse, + SyncCreativesResponse3, + ) + + response = TypeAdapter(SyncCreativesResponse).validate_python( + { + "status": "submitted", + "task_id": "task_123", + "message": "Batch ingestion queued", + } + ) + + assert isinstance(response, SyncCreativesResponse3) + assert response.status == "submitted" + assert response.task_id == "task_123" + + +def test_schema_derived_response_arms_preserve_nested_validation(): + """Schema-derived response arms should not widen structured fields to Any.""" + from datetime import date + + import pytest + from pydantic import ValidationError + + from adcp.types.generated_poc.account.get_account_financials_response import Invoice + from adcp.types.generated_poc.brand.get_brand_identity_response import ( + File, + Fonts, + GetBrandIdentityResponse1, + ) + from adcp.types.generated_poc.content_standards.validate_content_delivery_response import ( + ValidateContentDeliveryResponse1, + ) + from adcp.types.generated_poc.creative.preview_creative_response import Input + + with pytest.raises(ValidationError): + GetBrandIdentityResponse1.model_validate( + {"brand_id": "b1", "house": {}, "names": [{"en": "Brand"}]} + ) + + with pytest.raises(ValidationError): + GetBrandIdentityResponse1.model_validate( + { + "brand_id": "b1", + "house": {"domain": "example.com", "name": "Example"}, + "names": [{"en": "Brand"}], + "tagline": 123, + } + ) + + with pytest.raises(ValidationError): + Fonts.model_validate({"primary": 123}) + + with pytest.raises(ValidationError): + File.model_validate({"url": "not-a-url"}) + + with pytest.raises(ValidationError): + Input.model_validate({}) + + with pytest.raises(ValidationError): + ValidateContentDeliveryResponse1.model_validate( + { + "summary": { + "total_records": "x", + "passed_records": 0, + "failed_records": 0, + }, + "results": [], + } + ) + + invoice = Invoice.model_validate( + {"invoice_id": "inv_1", "amount": 1, "status": "draft", "due_date": date(2026, 1, 1)} + ) + assert invoice.due_date == date(2026, 1, 1) + + with pytest.raises(ValidationError): + Invoice.model_validate( + {"invoice_id": "inv_1", "amount": 1, "status": "draft", "due_date": "not-a-date"} + ) + + +def test_post_generate_sync_creatives_response_arms_match_schema_creative_fields( + tmp_path, monkeypatch +): + """The post-generation response arms must stay aligned with the schema.""" + import ast + import json + from pathlib import Path + + from adcp._version import _read_packaged_version + from adcp.validation.version import resolve_bundle_key + from scripts import post_generate_fixes + + generated_dir = tmp_path / "generated_poc" + target = generated_dir / "creative" / "sync_creatives_response.py" + target.parent.mkdir(parents=True) + target.write_text( + "# generated by datamodel-codegen:\n" + "# filename: creative/sync_creatives_response.json\n\n" + "from __future__ import annotations\n\n" + "from ..core.version_envelope import AdcpVersionEnvelope\n\n\n" + "class SyncCreativesResponse(AdcpVersionEnvelope):\n" + " pass\n" + ) + monkeypatch.setattr(post_generate_fixes, "OUTPUT_DIR", generated_dir) + + post_generate_fixes.restore_response_variant_aliases() + + generated_source = target.read_text() + post_generate_fixes.restore_response_variant_aliases() + assert target.read_text() == generated_source + + compile(generated_source, str(target), "exec") + module = ast.parse(generated_source) + creative_class = next( + node for node in module.body if isinstance(node, ast.ClassDef) and node.name == "Creative" + ) + generated_fields = { + node.target.id + for node in creative_class.body + if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name) + } + + bundle_key = resolve_bundle_key(_read_packaged_version()) + schema_path = ( + Path("schemas") / "cache" / bundle_key / "creative" / "sync-creatives-response.json" + ) + schema = json.loads(schema_path.read_text()) + creative_schema = schema["oneOf"][0]["properties"]["creatives"]["items"] + + assert generated_fields >= set(creative_schema["properties"]) + + response_classes = { + node.name + for node in module.body + if isinstance(node, ast.ClassDef) and node.name.startswith("SyncCreativesResponse") + } + assert {"SyncCreativesResponse1", "SyncCreativesResponse2", "SyncCreativesResponse3"} <= ( + response_classes + ) + + def test_generated_types_can_import(): """Test that generated types module can be imported.""" from adcp.types import _generated as generated diff --git a/tests/test_decisioning_specialisms.py b/tests/test_decisioning_specialisms.py index 877adf13..771a49c4 100644 --- a/tests/test_decisioning_specialisms.py +++ b/tests/test_decisioning_specialisms.py @@ -502,38 +502,21 @@ def test_creative_builder_protocol_has_no_refine_creative() -> None: assert not hasattr(CreativeBuilderPlatform, "refine_creative") -def test_build_creative_response_has_no_submitted_arm() -> None: - """Regression-guard against ``adcontextprotocol/adcp#3392``: the - per-tool ``build-creative-response.json`` ``oneOf`` is strictly - Success | MultiSuccess | Error — no Submitted variant. Both the - JS and Python Protocols document ``build_creative`` as sync at - the wire level (slow generation pipelines await in-request; - status changes flow via ``publish_status_change``). - - When adcp#3392 lands and the spec rolls Submitted into the - ``oneOf``, this test breaks and forces a coordinated SDK update - to the Protocol return type (add ``BuildCreativeAsyncSubmitted`` - to the union).""" - # ``BuildCreativeResponse`` is a typing.Union of the discriminated - # arms. Walk its args and assert the wire-required field set - # doesn't include task-async submitted hints. +def test_build_creative_response_includes_submitted_arm() -> None: + """The spec now includes the task-submitted arm in build_creative responses.""" import typing from adcp.types import BuildCreativeResponse arms = typing.get_args(BuildCreativeResponse) assert len(arms) > 0, "BuildCreativeResponse should be a Union of arms" - for arm in arms: - # Build-creative arms carry creative_manifest / creative_manifests - # (Success/MultiSuccess) or errors (Error). None should declare - # task_id or status='submitted' — those are Submitted-arm hints. - if hasattr(arm, "model_fields"): - field_names = set(arm.model_fields.keys()) - assert "task_id" not in field_names, ( - f"BuildCreativeResponse arm {arm.__name__} unexpectedly carries " - "task_id — adcp#3392 may have landed; update the Protocol " - "return type to include the Submitted arm." - ) + submitted_arms = [ + arm + for arm in arms + if hasattr(arm, "model_fields") + and {"task_id", "status"}.issubset(set(arm.model_fields.keys())) + ] + assert [arm.__name__ for arm in submitted_arms] == ["BuildCreativeResponse6"] # ---- CreativeAdServerPlatform ---- diff --git a/tests/test_schema_validation.py b/tests/test_schema_validation.py index ef8a1d71..db2f2f62 100644 --- a/tests/test_schema_validation.py +++ b/tests/test_schema_validation.py @@ -29,6 +29,18 @@ class TestValidateRequest: + def _valid_create_media_buy_payload(self) -> dict: + return { + "proposal_id": "balanced_reach_q2", + "total_budget": {"amount": 50000, "currency": "USD"}, + "start_time": "2026-04-01T00:00:00Z", + "end_time": "2026-06-30T23:59:59Z", + "buyer_ref": "test-buyer-001", + "idempotency_key": "test-cmb-prop-001", + "brand": {"domain": "acmeoutdoor.example"}, + "account": {"account_id": "acct_demo"}, + } + def test_flags_missing_required_fields_with_json_pointer(self) -> None: outcome = validate_request("get_products", {}) assert outcome.valid is False @@ -58,6 +70,23 @@ def test_accepts_extension_fields_without_error(self) -> None: assert issue.pointer != "/unknown_vendor_field" assert not issue.pointer.startswith("/ext") + def test_start_timing_asap_selects_const_branch(self) -> None: + payload = self._valid_create_media_buy_payload() + payload["start_time"] = "asap" + + outcome = validate_request("create_media_buy", payload, version="3.1.0-rc.9") + + assert outcome.valid is True + + def test_start_timing_date_only_rejected_as_not_date_time(self) -> None: + payload = self._valid_create_media_buy_payload() + payload["start_time"] = "2026-04-01" + + outcome = validate_request("create_media_buy", payload, version="3.1.0-rc.9") + + assert outcome.valid is False + assert any(issue.pointer == "/start_time" for issue in outcome.issues) + class TestValidateResponse: def test_selects_submitted_variant_on_status(self) -> None: diff --git a/tests/test_type_aliases.py b/tests/test_type_aliases.py index d8e48d39..0ea25280 100644 --- a/tests/test_type_aliases.py +++ b/tests/test_type_aliases.py @@ -14,12 +14,16 @@ ActivateSignalSuccessResponse, BothPreviewRender, BuildCreativeErrorResponse, + BuildCreativeSubmittedResponse, BuildCreativeSuccessResponse, CreateMediaBuyErrorResponse, CreateMediaBuySuccessResponse, HtmlPreviewRender, InlineDaastAsset, InlineVastAsset, + SyncAudiencesSubmittedResponse, + SyncCatalogsSubmittedResponse, + SyncCreativesSubmittedResponse, UrlDaastAsset, UrlPreviewRender, UrlVastAsset, @@ -31,8 +35,12 @@ ActivateSignalResponse2, BuildCreativeResponse1, BuildCreativeResponse2, + BuildCreativeResponse6, CreateMediaBuyResponse1, CreateMediaBuyResponse2, + SyncAudiencesResponse3, + SyncCatalogsResponse3, + SyncCreativesResponse3, ) # Test that aliases can also be imported from the aliases module @@ -45,6 +53,9 @@ from adcp.types.aliases import ( BuildCreativeErrorResponse as AliasBuildCreativeErrorResponse, ) +from adcp.types.aliases import ( + BuildCreativeSubmittedResponse as AliasBuildCreativeSubmittedResponse, +) from adcp.types.aliases import ( BuildCreativeSuccessResponse as AliasBuildCreativeSuccessResponse, ) @@ -69,8 +80,12 @@ def test_aliases_point_to_correct_types(): assert ActivateSignalErrorResponse is ActivateSignalResponse2 assert BuildCreativeSuccessResponse is BuildCreativeResponse1 assert BuildCreativeErrorResponse is BuildCreativeResponse2 + assert BuildCreativeSubmittedResponse is BuildCreativeResponse6 assert CreateMediaBuySuccessResponse is CreateMediaBuyResponse1 assert CreateMediaBuyErrorResponse is CreateMediaBuyResponse2 + assert SyncAudiencesSubmittedResponse is SyncAudiencesResponse3 + assert SyncCatalogsSubmittedResponse is SyncCatalogsResponse3 + assert SyncCreativesSubmittedResponse is SyncCreativesResponse3 def test_aliases_from_main_module_match_aliases_module(): @@ -79,6 +94,7 @@ def test_aliases_from_main_module_match_aliases_module(): assert ActivateSignalErrorResponse is AliasActivateSignalErrorResponse assert BuildCreativeSuccessResponse is AliasBuildCreativeSuccessResponse assert BuildCreativeErrorResponse is AliasBuildCreativeErrorResponse + assert BuildCreativeSubmittedResponse is AliasBuildCreativeSubmittedResponse assert CreateMediaBuySuccessResponse is AliasCreateMediaBuySuccessResponse assert CreateMediaBuyErrorResponse is AliasCreateMediaBuyErrorResponse @@ -118,6 +134,7 @@ def test_all_response_aliases_exported(): # Build creative "BuildCreativeSuccessResponse", "BuildCreativeErrorResponse", + "BuildCreativeSubmittedResponse", # Create media buy "CreateMediaBuySuccessResponse", "CreateMediaBuyErrorResponse", @@ -127,6 +144,11 @@ def test_all_response_aliases_exported(): # Sync creatives "SyncCreativesSuccessResponse", "SyncCreativesErrorResponse", + "SyncCreativesSubmittedResponse", + # Sync catalogs + "SyncCatalogsSubmittedResponse", + # Sync audiences + "SyncAudiencesSubmittedResponse", # Update media buy "UpdateMediaBuySuccessResponse", "UpdateMediaBuyErrorResponse", diff --git a/tests/test_type_guards.py b/tests/test_type_guards.py index 21693625..8bf398be 100644 --- a/tests/test_type_guards.py +++ b/tests/test_type_guards.py @@ -5,9 +5,18 @@ from adcp.types.guards import ( is_adcp_error, is_adcp_success, + is_build_creative_error, + is_build_creative_submitted, + is_build_creative_success, is_create_media_buy_error, is_create_media_buy_submitted, is_create_media_buy_success, + is_sync_catalogs_error, + is_sync_catalogs_submitted, + is_sync_catalogs_success, + is_sync_creatives_error, + is_sync_creatives_submitted, + is_sync_creatives_success, is_update_media_buy_error, is_update_media_buy_submitted, is_update_media_buy_success, @@ -183,6 +192,75 @@ def test_update_media_buy_submitted_guard(self) -> None: ) assert is_update_media_buy_submitted(error) is False + def test_build_creative_submitted_guard(self) -> None: + """Submitted build_creative envelope is neither sync success nor error.""" + from adcp.types.aliases import ( + BuildCreativeErrorResponse, + BuildCreativeSubmittedResponse, + BuildCreativeSuccessResponse, + ) + + submitted = BuildCreativeSubmittedResponse.model_validate( + {"status": "submitted", "task_id": "task_build"} + ) + assert is_build_creative_submitted(submitted) is True + assert is_build_creative_success(submitted) is False + assert is_build_creative_error(submitted) is False + + success = BuildCreativeSuccessResponse.model_construct() + assert is_build_creative_success(success) is True + assert is_build_creative_submitted(success) is False + + error = BuildCreativeErrorResponse.model_construct(errors=[{"message": "fail"}]) + assert is_build_creative_error(error) is True + assert is_build_creative_submitted(error) is False + + def test_sync_creatives_submitted_guard(self) -> None: + """Submitted sync_creatives envelope is neither sync success nor error.""" + from adcp.types.aliases import ( + SyncCreativesErrorResponse, + SyncCreativesSubmittedResponse, + SyncCreativesSuccessResponse, + ) + + submitted = SyncCreativesSubmittedResponse.model_validate( + {"status": "submitted", "task_id": "task_creatives"} + ) + assert is_sync_creatives_submitted(submitted) is True + assert is_sync_creatives_success(submitted) is False + assert is_sync_creatives_error(submitted) is False + + success = SyncCreativesSuccessResponse.model_construct(creatives=[]) + assert is_sync_creatives_success(success) is True + assert is_sync_creatives_submitted(success) is False + + error = SyncCreativesErrorResponse.model_construct(errors=[{"message": "fail"}]) + assert is_sync_creatives_error(error) is True + assert is_sync_creatives_submitted(error) is False + + def test_sync_catalogs_submitted_guard(self) -> None: + """Submitted sync_catalogs envelope is neither sync success nor error.""" + from adcp.types.aliases import ( + SyncCatalogsErrorResponse, + SyncCatalogsSubmittedResponse, + SyncCatalogsSuccessResponse, + ) + + submitted = SyncCatalogsSubmittedResponse.model_validate( + {"status": "submitted", "task_id": "task_catalogs"} + ) + assert is_sync_catalogs_submitted(submitted) is True + assert is_sync_catalogs_success(submitted) is False + assert is_sync_catalogs_error(submitted) is False + + success = SyncCatalogsSuccessResponse.model_construct(catalogs=[]) + assert is_sync_catalogs_success(success) is True + assert is_sync_catalogs_submitted(success) is False + + error = SyncCatalogsErrorResponse.model_construct(errors=[{"message": "fail"}]) + assert is_sync_catalogs_error(error) is True + assert is_sync_catalogs_submitted(error) is False + class TestImportFromAdcp: """Test that guards are importable from top-level package.""" @@ -194,6 +272,7 @@ def test_import_generic_guards(self) -> None: assert callable(is_adcp_success) def test_import_typed_guards_from_types(self) -> None: - from adcp.types import is_create_media_buy_success + from adcp.types import is_create_media_buy_success, is_sync_creatives_submitted assert callable(is_create_media_buy_success) + assert callable(is_sync_creatives_submitted)