diff --git a/SCHEMA_DELTAS.md b/SCHEMA_DELTAS.md index 196921f3a..fa22a6cb0 100644 --- a/SCHEMA_DELTAS.md +++ b/SCHEMA_DELTAS.md @@ -1,359 +1,198 @@ # Generated-types delta +## Files removed + +- `compliance/task_completion_data.py` — ComplianceTaskCompletionData +- `core/async_response_data.py` — AdcpAsyncResponseData +- `core/pricing_option.py` — PricingOption +- `core/requirements/asset_requirements.py` — AssetRequirements +- `core/start_timing.py` — StartTiming + ## Field changes -- `account/list_account_changes_request.py` - - `ResourceType`: `+__slots__` -- `account/list_account_changes_response.py` - - `ResourceType`: `+__slots__` +- `a2ui/bound_value.py` + - **classes removed**: A2UiBoundValue - `adagents.py` - - `Country`: `+__slots__` - - `SignalId`: `+__slots__` - - `SignalTag`: `+__slots__` -- `brand/acquire_rights_request.py` - - `Country`: `+__slots__` -- `brand/get_rights_request.py` - - `Country`: `+__slots__` -- `brand/rights_terms.py` - - `Country`: `+__slots__` -- `brand/search_brands_request.py` - - `Country`: `+__slots__` -- `brand/search_brands_response.py` - - `Country`: `+__slots__` + - **classes removed**: AuthorizedAgents - `brand/verify_brand_claims_request.py` - - `Country`: `+__slots__` + - **classes removed**: ClaimEntry - `brand_discovery.py` - - `Brand1`: `+__slots__` - - `BrandId`: `+__slots__` - - `BrandIdModel`: `+__slots__` - - `ColorValue1`: `+__slots__` - - `Country`: `+__slots__` - - `Country1`: `+__slots__` - - `Domain`: `+__slots__` - - `Fallback`: `+__slots__` - - `FocalPointItem`: `+__slots__` - - `HexColor`: `+__slots__` - - `LocaleTag`: `+__slots__` - - `LogoId`: `+__slots__` - - `NiceClass`: `+__slots__` - - `OpentypeFeature`: `+__slots__` - - `PreferredAspectRatio`: `+__slots__` - - `WeightRangeItem`: `+__slots__` + - **classes removed**: BrandDiscovery - `bundled/protocol/get_adcp_capabilities_response.py` - - `Country`: `+__slots__` - - `ExperimentalFeature`: `+__slots__` - - `ExtensionsSupportedItem`: `+__slots__` - - `FocalPointItem`: `+__slots__` - - `MajorVersion`: `+__slots__` - - `MraidVersion`: `+__slots__` - - `PrimaryKey`: `+__slots__` - - `ProtocolMethodsSupportedForItem`: `+__slots__` - - `PublisherDomain`: `+__slots__` - - `ReaderCompatibilityItem`: `+__slots__` - - `RequiredForItem`: `+__slots__` - - `ResourceType`: `+__slots__` - - `SupportedLanguage`: `+__slots__` - - `SupportedTimezone`: `+__slots__` - - `SupportedVersion`: `+__slots__` - - `Transport`: `+__slots__` - - `Value`: `+__slots__` -- `compliance/comply_test_controller_request.py` - - `Suggestion`: `+__slots__` + - **classes removed**: AnyOf3, Tasks10 +- `collection/base_collection_source.py` + - **classes removed**: BaseCollectionSource - `compliance/comply_test_controller_response.py` - - `Suggestion`: `+__slots__` -- `core/acceptance_policy_profile_ids.py` - - `AcceptancePolicyProfileId`: `+__slots__` -- `core/account_authorization.py` - - `AllowedTask`: `+__slots__` - - `ScopeName`: `+__slots__` -- `core/account_change.py` - - `ChangedPath`: `+__slots__` + - **classes removed**: RecordedCalls +- `content_standards/artifact.py` + - **classes removed**: Assets +- `core/account_identity_change.py` + - **classes removed**: AccountIdentityChange - `core/account_identity_change_preview.py` - - `Blocker`: `+__slots__` -- `core/account_timezone_capability.py` - - `SupportedTimezone`: `+__slots__` -- `core/agent_reporting_destination_state.py` - - `PriorDestinationRef`: `+__slots__` -- `core/applicable_package_id.py` - - `ApplicablePackageId`: `+__slots__` + - **classes removed**: AccountIdentityChangePreview +- `core/account_ref.py` + - **classes removed**: AccountReference +- `core/activation_key.py` + - **classes removed**: ActivationKey +- `core/agent_reporting_destination.py` + - **classes removed**: AgentReportingDestination - `core/assets/asset_union.py` - - `FocalPointItem`: `+__slots__` - - `Gtin`: `+__slots__` - - `Jurisdiction`: `+__slots__` - - `MacroBearingUrl2`: `+__slots__` -- `core/assets/image_asset.py` - - `FocalPointItem`: `+__slots__` -- `core/attestation_evaluation.py` - - `ReasonCode`: `+__slots__` -- `core/brand_id.py` - - `BrandId`: `+__slots__` -- `core/brand_key.py` - - `Country`: `+__slots__` -- `core/brand_ref.py` - - `Country`: `+__slots__` -- `core/canonical_audience_evidence.py` - - `AttestationDigest`: `+__slots__` -- `core/canonical_audience_evidence_selection.py` - - `VerifiedAttestationDigest`: `+__slots__` + - **classes removed**: AssetVariant, MacroBearingUrl +- `core/attestation_issuer.py` + - **classes removed**: AttestationIssuer +- `core/attestation_subject.py` + - **classes removed**: AttestationSubject +- `core/audience_activation_method.py` + - **classes removed**: AudienceActivationMethod +- `core/audience_selector.py` + - **classes removed**: AudienceSelector +- `core/audience_source.py` + - **classes removed**: AudienceSource +- `core/brand_response_authorization_result.py` + - **classes removed**: BrandResponseAuthorizationResult +- `core/budget_allocation.py` + - **classes removed**: BudgetAllocation, OptimizationGoal +- `core/canonical_account_ref.py` + - **classes removed**: CanonicalAccountReference +- `core/canonical_budget_allocation.py` + - **classes removed**: CanonicalBudgetAllocation +- `core/canonical_media_buy_action.py` + - **classes removed**: CanonicalMediaBuyAction +- `core/canonical_optimization_goal.py` + - **classes removed**: CanonicalOptimizationGoal +- `core/canonical_placement.py` + - **classes removed**: CanonicalProductPlacement - `core/canonical_product.py` - - `MatchedGtin`: `+__slots__` - - `PublisherDomain`: `+__slots__` -- `core/capabilities_changed_webhook.py` - - `ChangedPath`: `+__slots__` -- `core/catalog.py` - - `Category`: `+__slots__` - - `Gtin`: `+__slots__` - - `Query`: `+__slots__` -- `core/creative_brief.py` - - `Jurisdiction`: `+__slots__` -- `core/creative_revision_id.py` - - `CreativeRevisionId`: `+__slots__` -- `core/daast_tracker_constraints.py` - - `DaastOffset`: `+__slots__` + - **classes removed**: PublisherProperty +- `core/canonical_reporting_commitment.py` + - **classes removed**: CanonicalReportingCommitment +- `core/collection_selection.py` + - **classes removed**: CollectionSelection +- `core/committed_metric.py` + - **classes removed**: CommittedMetric +- `core/creative_item.py` + - **classes removed**: CreativeItem +- `core/creative_localization_readback.py` + - **classes removed**: Variants - `core/data_provider_signal_selector.py` - - `SignalId`: `+__slots__` - - `SignalTag`: `+__slots__` -- `core/demographic_targeting_resolution.py` - - `IntervalId`: `+__slots__` -- `core/downstream_connection_requirement.py` - - `RequiredForItem`: `+__slots__` -- `core/experimental_feature_id.py` - - `ExperimentalFeatureId`: `+__slots__` -- `core/frequency_cap_impression_constraints.py` - - `AllowedValue`: `+__slots__` -- `core/frequency_cap_interval_constraints.py` - - `AllowedInterval`: `+__slots__` -- `core/geo_place_area.py` - - `Value`: `+__slots__` -- `core/geo_place_catalog_capability.py` - - `SupportedVersion`: `+__slots__` -- `core/geo_place_catalog_entry.py` - - `ParentLabel`: `+__slots__` -- `core/geo_place_requirement.py` - - `SystemVersion`: `+__slots__` -- `core/geo_region_requirement.py` - - `Value`: `+__slots__` -- `core/geo_region_support.py` - - `Value`: `+__slots__` -- `core/iana_timezone.py` - - `IanaTimezoneIdentifier`: `+__slots__` -- `core/locale_tag.py` - - `LanguageTag`: `+__slots__` + - **classes removed**: DataProviderSignalSelector +- `core/delivery_metric_aggregate.py` + - **classes removed**: DeliveryMetricAggregate +- `core/deployment.py` + - **classes removed**: Deployment +- `core/destination.py` + - **classes removed**: Destination +- `core/evaluator_spec.py` + - **classes removed**: EvaluatorSpec +- `core/format.py` + - **classes removed**: Assets30 +- `core/format_option_ref.py` + - **classes removed**: FormatOptionReference +- `core/geo_place_system.py` + - **classes removed**: GeographicPlaceIdentifierSystem +- `core/geo_place_type.py` + - **classes removed**: GeographicPlaceType +- `core/inventory_list_application.py` + - **classes removed**: InventoryListApplication - `core/macro_bearing_url.py` - - `MacroBearingUrl4`: `+__slots__` -- `core/media_buy_change_term_id.py` - - `MediaBuyChangeTermId`: `+__slots__` -- `core/media_buy_legacy_terms_ref.py` - - `MediaBuyTermsReference`: `+__slots__` -- `core/offering.py` - - `Country`: `+__slots__` - - `Region`: `+__slots__` -- `core/placement_presentation.py` - - `Color`: `+__slots__` -- `core/principal_declarations.py` - - `AsyncAdcpVersion`: `+__slots__` -- `core/principal_state.py` - - `DestinationRef`: `+__slots__` + - **classes removed**: MacroBearingUrl +- `core/missing_metric.py` + - **classes removed**: MissingMetric +- `core/offering_asset_group.py` + - **classes removed**: Items +- `core/optimization_goal.py` + - **classes removed**: OptimizationGoal +- `core/package_format_snapshot.py` + - **classes removed**: PackageFormatSnapshot +- `core/package_signal_targeting.py` + - **classes removed**: PackageSignalTargeting +- `core/performance_feedback_metric.py` + - **classes removed**: PerformanceFeedbackMetric +- `core/placement_selection.py` + - **classes removed**: PlacementSelection +- `core/postal_country_system.py` + - **classes removed**: PostalCountrySystem - `core/product.py` - - `Country`: `+__slots__` - - `MatchedGtin`: `+__slots__` - - `PublisherDomain`: `+__slots__` - - `SupportedViewDuration`: `+__slots__` + - **classes removed**: PublisherProperty +- `core/product_audience_evidence_requirements.py` + - **classes removed**: AcceptedAttestationIssuers - `core/product_execution_requirement.py` - - `RequiredForItem`: `+__slots__` + - **classes removed**: ProductExecutionRequirement - `core/product_filters.py` - - `Country`: `+__slots__` - - `PricingCurrency`: `+__slots__` - - `Region`: `+__slots__` -- `core/product_offer_filters.py` - - `Country`: `+__slots__` - - `PricingCurrency`: `+__slots__` - - `Region`: `+__slots__` -- `core/property_id.py` - - `PropertyId`: `+__slots__` -- `core/property_tag.py` - - `PropertyTag`: `+__slots__` + - **classes removed**: SignalTargetingItem +- `core/product_format_declaration.py` + - **classes removed**: ProductFormatDeclaration - `core/publisher_property_selector.py` - - `PublisherDomain`: `+__slots__` + - **classes removed**: PublisherPropertySelector - `core/registry_event.py` - - `Domain`: `+__slots__` - - `Market`: `+__slots__` -- `core/reporting_adjustment_receipt.py` - - `ReportingAdjustmentRejectionCode`: `+__slots__` -- `core/reporting_canonicalization_contract.py` - - `ReportingPrimaryKey`: `+__slots__` -- `core/reporting_coverage.py` - - `ReportingMediaBuyId`: `+__slots__` - - `ReportingPackageId`: `+__slots__` -- `core/reporting_delivery_offering_id.py` - - `ReportingDeliveryOfferingId`: `+__slots__` -- `core/reporting_file_object_ref.py` - - `ReportingFileObjectReference`: `+__slots__` -- `core/reporting_native_version_ref.py` - - `ReportingNativeVersionReference`: `+__slots__` -- `core/reporting_receipt.py` - - `RejectionCode`: `+__slots__` -- `core/reporting_report_definition.py` - - `Dimension`: `+__slots__` -- `core/reporting_resource.py` - - `ReportingReaderCompatibilityItem`: `+__slots__` -- `core/reporting_status_changed_webhook.py` - - `IssueId`: `+__slots__` -- `core/requirements/audio_asset_requirements.py` - - `SampleRate`: `+__slots__` -- `core/requirements/image_asset_requirements.py` - - `PixelRatio`: `+__slots__` -- `core/requirements/video_asset_requirements.py` - - `AudioSampleRate`: `+__slots__` - - `FrameRate`: `+__slots__` -- `core/rights_constraint.py` - - `Country`: `+__slots__` - - `Restriction`: `+__slots__` -- `core/signal_coverage_forecast.py` - - `Country`: `+__slots__` -- `core/signal_definition.py` - - `Country`: `+__slots__` - - `Tag`: `+__slots__` -- `core/signal_definition_enrichment.py` - - `Country`: `+__slots__` + - **classes removed**: RegistryEvent +- `core/reporting_control_total.py` + - **classes removed**: ReportingControlTotal +- `core/reporting_dataset_share_destination.py` + - **classes removed**: ReportingDatasetShareDestination +- `core/reporting_delivery_method.py` + - **classes removed**: ReportingDeliveryMethod +- `core/reporting_write_destination.py` + - **classes removed**: ReportingWriteDestination +- `core/requirements/catalog_field_binding.py` + - **classes removed**: CatalogFieldBinding, PerItemBindings +- `core/signal_id.py` + - **classes removed**: SignalId +- `core/signal_pricing.py` + - **classes removed**: VendorPricing +- `core/signal_ref.py` + - **classes removed**: SignalRef +- `core/signal_targeting.py` + - **classes removed**: SignalTargeting +- `core/signal_targeting_expression.py` + - **classes removed**: SignalTargetingExpression - `core/targeting.py` - - `AxeExcludeSegment`: `+__slots__` - - `AxeIncludeSegment`: `+__slots__` - - `GeoCountry`: `+__slots__` - - `GeoRegion`: `+__slots__` -- `core/targeting_overlay_support.py` - - `SystemVersion`: `+__slots__` + - **classes removed**: CollectionListExclude, Demographics, PropertyListExclude +- `core/targeting_modification.py` + - **classes removed**: TargetingModification +- `core/tracker_execution_selector.py` + - **classes removed**: TrackerExecutionSelector - `core/transformer.py` - - `OutputCapabilityId`: `+__slots__` -- `core/vast_media_file_requirements.py` - - `Codec`: `+__slots__` - - `Container`: `+__slots__` - - `MimeType`: `+__slots__` -- `core/vast_tracker_constraints.py` - - `VastOffset`: `+__slots__` -- `core/vendor_metric_id.py` - - `VendorMetricId`: `+__slots__` + - **classes removed**: InputFormat - `core/vendor_pricing_option.py` - - `AppliesToOutputCapabilityId`: `+__slots__` -- `core/version_envelope.py` - - `AdcpMajorVersion`: `+__slots__` - - `AdcpVersion`: `+__slots__` -- `creative/list_transformers_request.py` - - `OutputCapabilityId`: `+__slots__` + - **classes removed**: VendorPricingOption +- `core/wholesale_feed_event.py` + - **classes removed**: AppliesTo, WholesaleFeedEvent +- `creative/preview_render.py` + - **classes removed**: PreviewRender - `creative/sync_creatives_request.py` - - `PlacementId`: `+__slots__` -- `error_details/accessibility_violation.py` - - `FailureKind1`: `+__slots__` -- `error_details/requote_required.py` - - `EnvelopeField`: `+__slots__` - - `EnvelopeField1Item`: `+__slots__` -- `error_details/unsupported_refinement_dimension.py` - - `SupportedDimension`: `+__slots__` + - **classes removed**: AssignmentOperations +- `creative/validate_input_request.py` + - **classes removed**: Targets +- `error_details/governance_agent_not_accepted.py` + - **classes removed**: GovernanceAgentNotAcceptedDetails - `error_details/vast_version_mismatch.py` - - `ObservedDocumentVastVersion`: `+__slots__` -- `error_details/version_unsupported.py` - - `SupportedMajor`: `+__slots__` - - `SupportedVersion`: `+__slots__` -- `formats/canonical/_base.py` - - `PixelRatio`: `+__slots__` -- `formats/canonical/audio_daast.py` - - `DurationMsRangeItem`: `+__slots__` -- `formats/canonical/audio_hosted.py` - - `AudioSampleRate`: `+__slots__` - - `DurationMsRange`: `+__slots__` -- `formats/canonical/audio_vast.py` - - `DurationMsRangeItem`: `+__slots__` -- `formats/canonical/coordinated_placements.py` - - `CompanionBannerWidth`: `+__slots__` - - `DurationMsRange`: `+__slots__` - - `PixelRatio`: `+__slots__` - - `SlotBinding`: `+__slots__` -- `formats/canonical/image.py` - - `PixelRatio`: `+__slots__` -- `formats/canonical/seller_rendered_stateful_display.py` - - `DurationMsRange`: `+__slots__` - - `SlotBinding`: `+__slots__` - - `WidthRangeItem`: `+__slots__` -- `formats/canonical/video_hosted.py` - - `CompanionBannerWidth`: `+__slots__` - - `DurationMsRange`: `+__slots__` -- `formats/canonical/video_vast.py` - - `DurationMsRangeItem`: `+__slots__` + - **classes removed**: VastVersionMismatchDetails - `governance/reported_outcome_error.py` - - `BoundedScalar1`: `+__slots__` -- `manifest_schema.py` - - `SchemaPath`: `+__slots__` - - `ToolName`: `+__slots__` -- `media_buy/acceptance_context.py` - - `AdvertiserJurisdiction`: `+__slots__` - - `SubjectFacet`: `+__slots__` -- `media_buy/acceptance_policy_profile.py` - - `Jurisdiction`: `+__slots__` - - `JurisdictionGroup`: `+__slots__` - - `SubjectCategory`: `+__slots__` + - **classes removed**: BoundedValue, BoundedValueLevel2, BoundedValueLevel3 - `media_buy/acceptance_policy_requirement.py` - - `Criterion`: `+__slots__` - - `FormatId`: `+__slots__` -- `media_buy/acceptance_policy_rule.py` - - `Jurisdiction`: `+__slots__` - - `JurisdictionGroup`: `+__slots__` - - `PolicyId`: `+__slots__` - - `SubjectFacet`: `+__slots__` + - **classes removed**: AcceptancePolicyRequirement - `media_buy/build_creative_request.py` - - `TargetCapabilityId`: `+__slots__` -- `media_buy/change_term.py` - - `Condition`: `+__slots__` -- `media_buy/control_media_buy_response.py` - - `AffectedPackageId`: `+__slots__` -- `media_buy/get_products_rejected.py` - - `Suggestion`: `+__slots__` + - **classes removed**: SignalCondition +- `media_buy/change_term_constraints.py` + - **classes removed**: MediaBuyChangeTermConstraints +- `media_buy/decline_proposals_response.py` + - **classes removed**: Results, Results3 +- `media_buy/get_products_request.py` + - **classes removed**: Refine - `media_buy/get_products_response.py` - - `Suggestion`: `+__slots__` -- `media_buy/get_reporting_status_request.py` - - `DeliveryConfigId`: `+__slots__` -- `media_buy/legacy_purchase_continuation_input.py` - - `SelectedProductId`: `+__slots__` -- `media_buy/package_control.py` - - `CatalogId`: `+__slots__` -- `media_buy/product_discovery_criteria.py` - - `ProductId`: `+__slots__` + - **classes removed**: RefinementApplied +- `media_buy/package_request.py` + - **classes removed**: CommittedMetrics - `media_buy/product_purchase.py` - - `AgencyEstimateNumber`: `+__slots__` - - `Budget`: `+__slots__` - - `CatalogId`: `+__slots__` - - `DailyBudgetCap`: `+__slots__` - - `MinSpendTarget`: `+__slots__` + - **classes removed**: Bidding, Context, Ext, Pricing +- `media_buy/product_refinement.py` + - **classes removed**: ProductRefinementRequests1 +- `media_buy/proposal_refinement.py` + - **classes removed**: ProposalRefinement - `media_buy/refine_proposals_response.py` - - `UnsatisfiedConstraint`: `+__slots__` -- `media_buy/request_proposals_response.py` - - `Suggestion`: `+__slots__` -- `media_buy/sync_audiences_request.py` - - `Tag`: `+__slots__` -- `media_buy/sync_event_sources_request.py` - - `ValueCurrency`: `+__slots__` -- `pricing_options/cpv_option.py` - - `ViewThreshold`: `+__slots__` -- `property/property_list_filters.py` - - `CountriesAllItem`: `+__slots__` -- `protocol/get_adcp_capabilities_response.py` - - `DefaultProfileId`: `+__slots__` - - `ExtensionsSupportedItem`: `+__slots__` - - `MajorVersion`: `+__slots__` - - `MraidVersion`: `+__slots__` - - `PrimaryCountry`: `+__slots__` - - `ProtocolMethodsSupportedForItem`: `+__slots__` - - `PublisherDomain`: `+__slots__` - - `RequiredForItem`: `+__slots__` - - `ResourceType`: `+__slots__` - - `SupportedVersion`: `+__slots__` - - `Transport`: `+__slots__` -- `signals/get_signals_request.py` - - `Country`: `+__slots__` -- `signals/get_signals_response.py` - - `Country`: `+__slots__` -- `trusted_match/context_match_request.py` - - `Keyword`: `+__slots__` + - **classes removed**: Results14, Results8 +- `property/base_property_source.py` + - **classes removed**: BasePropertySource - `trusted_match/provider_registration.py` - - `Country`: `+__slots__` - - `TmpxMacro`: `+__slots__` - - `TmpxSlot`: `+__slots__` + - **classes removed**: TmpProviderRegistration diff --git a/scripts/post_generate_fixes.py b/scripts/post_generate_fixes.py index 9e111f3de..3404cf058 100644 --- a/scripts/post_generate_fixes.py +++ b/scripts/post_generate_fixes.py @@ -811,17 +811,23 @@ def _remove_unused_pydantic_field_import(source: str) -> tuple[str, bool]: def remove_unused_pydantic_field_imports() -> None: - """Remove spurious ``Field`` imports emitted for generated enum modules.""" + """Remove ``Field`` and ``RootModel`` imports the generated module never uses. + + ``Field`` is spurious on generated enum modules. ``RootModel`` is left behind + by ``unwrap_rootmodel_unions``, which deliberately edits no imports: a fix + between it and here matches an exact generated import line, so the line has + to stay intact until every fix has run. + """ modified_files = 0 for py_path in OUTPUT_DIR.rglob("*.py"): source = py_path.read_text() - updated, changed = _remove_unused_pydantic_field_import(source) + updated, changed = _remove_unused_imports(source, {"pydantic": ("Field", "RootModel")}) if not changed: continue py_path.write_text(updated) modified_files += 1 - print(f" Removed unused pydantic.Field imports from {modified_files} file(s)") + print(f" Removed unused pydantic imports from {modified_files} file(s)") def _find_indented_field_block(content: str, field_name: str) -> tuple[int, int] | None: @@ -910,183 +916,435 @@ def fix_constr_type_annotations(): print(" No constrained string annotations needed fixing") -# Types to unwrap from RootModel to Union type alias. -# Only genuine discriminated unions (different field shapes per variant) belong here. -# "Validation-only" oneOf types (same fields, different required combos) are now -# handled at the schema level by flatten_validation_oneof() in generate_types.py, -# which produces a single BaseModel class — no RootModel or unwrapping needed. -# Removed from this set (now single classes): GetCreativeDeliveryRequest, -# GetSignalsRequest, ProvidePerformanceFeedbackRequest, SiSendMessageRequest, -# UpdateMediaBuyRequest. -# See: https://github.com/adcontextprotocol/adcp-client-python/issues/155 -_UNWRAP_TO_UNION: set[str] = { - "AcceptProposalResponse", - # Value type, unlike the rest of this set. AdCP 3.2.0-rc.3 replaced the - # plain MediaBuyValidAction enum on ProductAllowedAction.action, - # MediaBuyAvailableAction.action, and ActionNotAllowed.attempted_action - # with this named anyOf of that enum plus one const. Keeping the RootModel - # wrapper would break every adopter that writes action="pause" or - # action=MediaBuyValidAction.pause -- a wrapper the schema does not ask - # for, since anyOf of an enum and a const is a plain union. The root - # field's description/title are documentation only; each use site carries - # its own description. - "MediaBuyAvailableActionId", - "BuyProductsResponse", - "CheckGovernanceRequest", - "ControlMediaBuyResponse", - "DeclineProposalsResponse", - "ListProductsResponse", - "MediaBuyCommitmentResponse", - "RefineProposalsResponse", - "RequestProposalsResponse", - "AcquireRightsResponse", - "ComplyTestControllerRequest", - "ComplyTestControllerResponse", - "ActivateSignalResponse", - "BuildCreativeRequest", - "BuildCreativeResponse", - "CalibrateContentResponse", - "CreateContentStandardsResponse", - "CreateMediaBuyResponse", - "CreativeApprovalResponse", - "GetAccountFinancialsResponse", - "GetBrandIdentityResponse", - "GetContentStandardsResponse", - "GetCreativeFeaturesResponse", - "GetMediaBuyArtifactsResponse", - "GetPlanAuditLogsRequest", - "GetProductsRequest", - "GetRightsResponse", - "ListContentStandardsResponse", - "LogEventResponse", - "PreviewCreativeRequest", - "PreviewCreativeResponse", - "ProvidePerformanceFeedbackResponse", - "SyncAccountsResponse", - "SyncAudiencesResponse", - "SyncGovernanceResponse", - "SyncCatalogsResponse", - "SyncCreativesResponse", - "SyncEventSourcesResponse", - "UpdateContentStandardsResponse", - "UpdateMediaBuyResponse", - "UpdateRightsResponse", - "ValidateContentDeliveryResponse", -} +class _ComposingRootSpec(NamedTuple): + """One generated ``RootModel`` whose root composes rather than holds a value.""" + name: str + root: str + metadata: str | None + is_union: bool + docstring: str | None + description: str | None + start_line: int + end_line: int -def unwrap_rootmodel_unions(): - """Unwrap specified RootModel unions to plain Union type aliases. - Consumers that subclass library types cannot extend RootModel subclasses - because Pydantic 2 forbids model_config overrides on RootModel. +# A root spelled as one of these is a value, and the wrapper is the only place +# its constraints can live. ``list[...]``, ``dict[...]`` and ``Literal[...]`` are +# recognized by their subscript instead. +_VALUE_ROOT_NAMES = frozenset( + { + "Any", + "AnyUrl", + "AwareDatetime", + "Decimal", + "EmailStr", + "NaiveDatetime", + "bool", + "bytes", + "date", + "datetime", + "float", + "int", + "str", + "time", + } +) - Uses AST to find class definitions instead of regex, which avoids issues - with nested brackets in base class annotations. - Replaces: - class TypeName(RootModel[Variant1 | Variant2]): - root: Annotated[Variant1 | Variant2, Field(...)] - def __getattr__(self, name): ... +def _is_docstring(stmt: ast.stmt) -> bool: + """Whether ``stmt`` is a bare string expression.""" + return ( + isinstance(stmt, ast.Expr) + and isinstance(stmt.value, ast.Constant) + and isinstance(stmt.value.value, str) + ) - With: - TypeName = Variant1 | Variant2 - Note: the types in _UNWRAP_TO_UNION are Request/Response types whose - root: fields had no meaningful Field(description=..., examples=[...]) - metadata. Value-type RootModels that carry rich metadata are otherwise - intentionally excluded and keep the RootModel wrapper + __getattr__ proxy; - MediaBuyAvailableActionId is the documented exception (see the set). +def _root_is_value(node: ast.expr) -> bool: + """Whether a ``RootModel[...]`` argument names a value rather than a model.""" + if isinstance(node, ast.Subscript): + return True + if isinstance(node, ast.Name): + return node.id in _VALUE_ROOT_NAMES + if isinstance(node, ast.Attribute): + return node.attr in _VALUE_ROOT_NAMES + return True + + +def _composing_root_spec(source: str, node: ast.ClassDef) -> _ComposingRootSpec | None: + """Describe the rewrite for a composing ``class X(RootModel[...])``, or ``None``. + + Composing means a union of models or a single model. ``None`` for a value + root — ``RootModel[str]``, ``RootModel[list[A]]``, ``RootModel[Literal[...]]``, + ``RootModel[AnyUrl]`` — because the wrapper is the only place a value root's + constraints can live. ``None`` as well for a class carrying a decorator, a + class keyword, a validator, a default or a second body statement: dropping + any of those silently would change behavior. """ - unwrapped_count = 0 + if len(node.bases) != 1 or node.keywords or node.decorator_list: + return None + base = node.bases[0] + if not isinstance(base, ast.Subscript): + return None + if not (isinstance(base.value, ast.Name) and base.value.id == "RootModel"): + return None + is_union = isinstance(base.slice, ast.BinOp) and isinstance(base.slice.op, ast.BitOr) + if not is_union and _root_is_value(base.slice): + return None + if not node.end_lineno: + return None - for py_file in OUTPUT_DIR.rglob("*.py"): - with open(py_file) as f: - content = f.read() + docstring = next( + (ast.get_source_segment(source, stmt.value) for stmt in node.body if _is_docstring(stmt)), + None, + ) + body = [stmt for stmt in node.body if not _is_docstring(stmt)] + if len(body) != 1: + return None + field = body[0] + if not ( + isinstance(field, ast.AnnAssign) + and isinstance(field.target, ast.Name) + and field.target.id == "root" + and field.value is None + ): + return None - if "RootModel[" not in content: - continue + annotation = field.annotation + metadata: str | None = None + description: str | None = None + if ( + isinstance(annotation, ast.Subscript) + and isinstance(annotation.value, ast.Name) + and annotation.value.id == "Annotated" + ): + elements = list(annotation.slice.elts) if isinstance(annotation.slice, ast.Tuple) else [] + if len(elements) != 2: + return None + annotation, candidate = elements + if not ( + isinstance(candidate, ast.Call) + and isinstance(candidate.func, ast.Name) + and candidate.func.id == "Field" + ): + return None + metadata = ast.get_source_segment(source, candidate) + if metadata is None: + return None + description = next( + ( + keyword.value.value + for keyword in candidate.keywords + if keyword.arg == "description" + and isinstance(keyword.value, ast.Constant) + and isinstance(keyword.value.value, str) + ), + None, + ) + + # The ``root:`` annotation restates the base's argument; emit the restated + # one, which is the form the field metadata was written against. + if is_union != (isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr)): + return None + root = ast.get_source_segment(source, annotation) + if root is None: + return None + + return _ComposingRootSpec( + name=node.name, + root=_dedent_continuation(root), + metadata=None if metadata is None else _dedent_continuation(metadata), + is_union=is_union, + docstring=docstring, + description=description, + start_line=node.lineno, + end_line=node.end_lineno, + ) + + +def _dedent_continuation(segment: str, columns: int = 4) -> str: + """Shift a source segment's continuation lines left by ``columns``. + + A source segment lifted out of ``root: Annotated[...]`` carries that field's + indentation. The alias it becomes sits one level further out. + """ + first, *rest = segment.split("\n") + return "\n".join( + [first, *(line[columns:] if line[:columns].isspace() else line for line in rest)] + ) + +def _union_arm_names(node: ast.expr, into: set[str]) -> None: + """Collect the names a union expression joins, by their final component.""" + if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr): + _union_arm_names(node.left, into) + _union_arm_names(node.right, into) + elif isinstance(node, ast.Name): + into.add(node.id) + elif isinstance(node, ast.Attribute): + into.add(node.attr) + + +def _plain_model_names(output_dir: Path) -> set[str]: + """Names the generated tree declares only as a plain model class. + + A single-model root becomes a subclass of its target, so the target has to + be subclassable. An enumeration with members is not, and neither is a target + that is itself a ``RootModel`` union, which this pass turns into a union + alias. A name declared both ways anywhere in the tree is excluded. + """ + declared: set[str] = set() + excluded: set[str] = set() + for py_file in output_dir.rglob("*.py"): + source = py_file.read_text() try: - tree = ast.parse(content) + tree = ast.parse(source) except SyntaxError: continue + for node in ast.walk(tree): + if not isinstance(node, ast.ClassDef): + continue + declared.add(node.name) + bases = [ast.get_source_segment(source, base) or "" for base in node.bases] + if any( + base in ("Enum", "StrEnum", "IntEnum") or base.startswith("RootModel[") + for base in bases + ): + excluded.add(node.name) + return declared - excluded - original = content - lines = content.split("\n") - # Collect classes to unwrap (process in reverse order to preserve line numbers) - replacements: list[tuple[int, int, str, str]] = ( - [] - ) # (start_line, end_line, name, union_types) +def _discriminated_arm_names(output_dir: Path) -> set[str]: + """Names the generated tree uses as an arm of a discriminated union. + A wrapper named there is the one choice the parent's discriminator maps its + tag to. ``AssetVariant`` discriminates 21 arms on ``asset_type``, and three + of them — ``VastAsset``, ``DaastAsset``, ``DisplayTagAsset`` — are each a + union of shapes that share one ``asset_type`` and differ on a second + property. Replacing one of those with its own union inlines both shapes + under the same tag, and Pydantic refuses the parent union at import time. + """ + names: set[str] = set() + for py_file in output_dir.rglob("*.py"): + content = py_file.read_text() + if "discriminator=" not in content: + continue + try: + tree = ast.parse(content) + except SyntaxError: + continue for node in ast.walk(tree): - if not isinstance(node, ast.ClassDef) or node.name not in _UNWRAP_TO_UNION: + if not ( + isinstance(node, ast.Subscript) + and isinstance(node.value, ast.Name) + and node.value.id == "Annotated" + and isinstance(node.slice, ast.Tuple) + and len(node.slice.elts) >= 2 + ): continue - if not node.end_lineno: + union, *metadata = node.slice.elts + if not (isinstance(union, ast.BinOp) and isinstance(union.op, ast.BitOr)): continue - - # Find the RootModel[...] base class using AST source segments - for base in node.bases: - base_src = ast.get_source_segment(content, base) - if not base_src or "RootModel[" not in base_src: + if any( + isinstance(entry, ast.Call) + and any(keyword.arg == "discriminator" for keyword in entry.keywords) + for entry in metadata + ): + _union_arm_names(union, names) + return names + + +def _field_union_arm_names(output_dir: Path) -> set[str]: + """Names any module's *field* unions join, when the union has two or more arms. + + The same-module guard above is not enough on its own: a composing root + that another module's field names as an arm of a smart (non-discriminated) + union — ``assets: LocalizedCreativeAsset | Assets`` in + ``core/creative_localization.py``, where ``LocalizedCreativeAsset`` is a + ``oneOf`` root defined in its own module — would be unwrapped, and the + field's union would then compare the root's arms against ``Assets`` + directly. Pydantic's smart mode scores the successful arms (fields set, + exactness), so inlining can select a different arm for the same payload, + silently. The wrapper keeps the root one choice, as it was. + + Only field annotations count, and only for union roots: a single-model root + is one class whichever way it is spelled. A module-level union *root* that names other + roots as arms — ``core/async-response-data.json`` composing every task + response — is a union of unions by schema and was flattened before this + pass existed; keeping its arms wrapped would re-wrap unions adopters + already use unwrapped. Discriminated roots are exempt as well: a tagged + union nested as an arm is selected by its tag, not scored. + """ + names: set[str] = set() + for py_file in output_dir.rglob("*.py"): + content = py_file.read_text() + if "|" not in content: + continue + try: + tree = ast.parse(content) + except SyntaxError: + continue + for node in ast.walk(tree): + if not isinstance(node, ast.ClassDef): + continue + for statement in node.body: + if not isinstance(statement, ast.AnnAssign): continue + # A wrapper's own ``root:`` annotation restates a root union; + # it is the composition this pass rewrites, not a field that + # scores arms. + if isinstance(statement.target, ast.Name) and statement.target.id == "root": + continue + for union in ast.walk(statement.annotation): + if not (isinstance(union, ast.BinOp) and isinstance(union.op, ast.BitOr)): + continue + arms: set[str] = set() + _union_arm_names(union, arms) + if len(arms - {"None"}) >= 2: + names.update(arms) + return names + + +def _module_union_arm_names(tree: ast.Module) -> set[str]: + """Names a union inside this module joins. + + A wrapper named as an arm of another union in the same module is that + union's single choice, and its own arms are not siblings of the other + arms. Inlining it makes them siblings, and Pydantic then picks a + different arm for the same payload: ``AdcpAgentsAuthorization2`` groups + 14 catalog shapes under one choice of ``AdcpAgentsAuthorization``, and + ``VastAsset`` groups two delivery shapes under one choice of + ``AssetVariant``. + """ + names: set[str] = set() + for node in ast.walk(tree): + if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr): + _union_arm_names(node, names) + return names - # Extract union type from RootModel[...] using bracket depth - # to handle nested generics like RootModel[list[X] | Y] - bracket_start = base_src.index("RootModel[") + len("RootModel[") - depth = 1 - pos = bracket_start - while pos < len(base_src) and depth > 0: - if base_src[pos] == "[": - depth += 1 - elif base_src[pos] == "]": - depth -= 1 - pos += 1 - bracket_end = pos - 1 # position of the matching ] - union_types = base_src[bracket_start:bracket_end].strip() - - replacements.append((node.lineno, node.end_lineno, node.name, union_types)) - break - if not replacements: +def _ensure_typing_import(content: str, name: str) -> str: + """Add ``name`` to the module's ``typing`` import when it is missing.""" + if re.search(rf"^from typing import .*\b{name}\b", content, re.MULTILINE): + return content + match = re.search(r"^from typing import ", content, re.MULTILINE) + if match: + return content[: match.end()] + f"{name}, " + content[match.end() :] + return f"from typing import {name}\n" + content + + +def unwrap_rootmodel_unions(): + """Emit a composing schema root as what it composes, metadata intact. + + Pydantic 2 refuses ``model_config['extra']`` on a ``RootModel`` subclass, so + a consumer cannot extend a generated ``RootModel`` the way it extends every + other generated model. A root that composes — a union of object models, or a + single model — has no value semantics the wrapper protects. + + A union root becomes a union type alias carrying the generator's own + ``Field(...)``, which keeps the description, the title and — where the schema + declares one — the ``discriminator``:: + + class TypeName(RootModel[Variant1 | Variant2]): + root: Annotated[Variant1 | Variant2, Field(discriminator='type')] + + TypeName = Annotated[Variant1 | Variant2, Field(discriminator='type')] + + A single-model root becomes a subclass of that model, which keeps the name + constructible and moves the root description to the class docstring, where + Pydantic reads it for the JSON schema:: + + class TypeName(RootModel[Target]): + root: Annotated[Target, Field(description='...')] + + class TypeName(Target): + # the root description, as a docstring + + An unannotated union root becomes the bare union. A value root — ``str``, + ``AnyUrl``, ``list[X]``, ``Literal[...]`` — keeps its ``RootModel``, and so + do a wrapper another union discriminates against and a root naming an + enumeration. + + See https://github.com/adcontextprotocol/adcp-client-python/issues/1077. + """ + unwrapped_count = 0 + kept_count = 0 + keep_wrapped = _discriminated_arm_names(OUTPUT_DIR) + field_union_arms = _field_union_arm_names(OUTPUT_DIR) + plain_models = _plain_model_names(OUTPUT_DIR) + + for py_file in OUTPUT_DIR.rglob("*.py"): + content = py_file.read_text() + + if "RootModel[" not in content: continue - # Apply replacements in reverse line order to preserve indices - for start_line, end_line, type_name, union_types in sorted(replacements, reverse=True): - # Wrap multi-line unions in parentheses for valid syntax - if "\n" in union_types: - # Re-indent continuation lines to 4 spaces - union_lines = [ln.strip() for ln in union_types.split("\n")] - indented = union_lines[0] + "\n" + "\n".join(f" {ln}" for ln in union_lines[1:]) - replacement = f"{type_name} = (\n {indented}\n)" - else: - replacement = f"{type_name} = {union_types}" - lines[start_line - 1 : end_line] = [replacement] - unwrapped_count += 1 + try: + tree = ast.parse(content) + except SyntaxError: + continue + candidates = [ + spec + for node in ast.walk(tree) + if isinstance(node, ast.ClassDef) + for spec in [_composing_root_spec(content, node)] + if spec is not None + ] + module_arms = _module_union_arm_names(tree) + specs = [ + spec + for spec in candidates + if spec.name not in keep_wrapped + and spec.name not in module_arms + and not ( + spec.is_union + and spec.name in field_union_arms + and (spec.metadata is None or "discriminator=" not in spec.metadata) + ) + and (spec.is_union or spec.root.split(".")[-1] in plain_models) + ] + kept_count += len(candidates) - len(specs) + if not specs: + continue + + lines = content.split("\n") + for spec in sorted(specs, key=lambda s: s.start_line, reverse=True): + lines[spec.start_line - 1 : spec.end_line] = [_composing_root_source(spec)] + unwrapped_count += 1 content = "\n".join(lines) - if content != original: - # Remove RootModel from imports if no longer used as a base class - if not re.search(r"\(RootModel\[", content): - content = re.sub(r",\s*RootModel", "", content) - content = re.sub(r"RootModel,\s*", "", content) - - # Remove unused Any import if no longer referenced in code body - import_line_end = content.find("\n", content.find("from typing import")) - after_imports = content[import_line_end:] if import_line_end > 0 else "" - if "Any" not in after_imports: - content = re.sub(r"Any,\s*", "", content) - content = re.sub(r",\s*Any", "", content) + if any(spec.is_union and spec.metadata is not None for spec in specs): + content = _ensure_typing_import(content, "Annotated") - with open(py_file, "w") as f: - f.write(content) + # Imports stay as generated. A later fix matches an exact import line, + # so ``remove_unused_pydantic_field_imports`` drops the now-unused + # ``RootModel`` once every fix has run. + py_file.write_text(content) if unwrapped_count > 0: - print(f" Unwrapped {unwrapped_count} RootModel union(s) to type aliases") + print( + f" Unwrapped {unwrapped_count} composing RootModel root(s); " + f"kept {kept_count} the wrapper still carries" + ) else: - print(" No RootModel unions needed unwrapping") + print(" No RootModel roots needed unwrapping") + + +def _composing_root_source(spec: _ComposingRootSpec) -> str: + """Render the replacement for one composing ``RootModel`` class.""" + if not spec.is_union: + docstring = spec.docstring or (None if spec.description is None else repr(spec.description)) + body = "pass" if docstring is None else docstring + return f"class {spec.name}({spec.root}):\n {body}" + if spec.metadata is None: + # A multi-line union needs brackets of its own to stay one expression; + # ``Annotated[...]`` supplies them in the other arm. + if "\n" in spec.root: + return f"{spec.name} = (\n {spec.root}\n)" + return f"{spec.name} = {spec.root}" + return f"{spec.name} = Annotated[\n {spec.root},\n {spec.metadata},\n]" def add_rootmodel_getattr_proxy(): @@ -1117,16 +1375,25 @@ def add_rootmodel_getattr_proxy(): else: source = "from typing import Any\n" + source + # The method goes at the class's own end, which the AST reports. A + # module now holds union type aliases between its classes, so "just + # before the next ``class``" would land the method at module indent. + try: + tree = ast.parse(source) + except SyntaxError: + continue + line_offsets = [0] + for line in source.splitlines(keepends=True): + line_offsets.append(line_offsets[-1] + len(line)) + insertions: list[int] = [] - for match in re.finditer(r"^class ([A-Za-z_]\w*)\b", source, re.MULTILINE): - header_end = source.find(":\n", match.end()) - if header_end == -1: + for node in ast.walk(tree): + if not isinstance(node, ast.ClassDef) or not node.end_lineno: continue - header = source[match.start() : header_end] + header = " ".join(ast.get_source_segment(source, base) or "" for base in node.bases) if "RootModel[" not in header or "|" not in header: continue - next_class = re.compile(r"^class ", re.MULTILINE).search(source, header_end + 2) - insertions.append(next_class.start() if next_class is not None else len(source)) + insertions.append(line_offsets[node.end_lineno]) if not insertions: continue @@ -2049,83 +2316,6 @@ def _intersection_field(field: str, keep_base: str, all_bases: list[str]) -> str print(" No allOf-merge field override conflicts found") -def expose_account_reference_union_fields() -> None: - """Replace generated AccountReference wrappers with their concrete arms. - - ``AccountReference`` is public as a composable object-union alias, but - datamodel-codegen still annotates every schema reference with its outer - ``RootModel`` class. Rewrite those generated annotations at the source so - request, nested-input, response, and canonical-clone paths all expose the - same concrete arm types without import-time Pydantic patching. - """ - account_ref_source = OUTPUT_DIR / "core" / "account_ref.py" - if not account_ref_source.exists(): - print(" account reference model not found (skipping union-field fix)") - return - - tree = ast.parse(account_ref_source.read_text()) - wrapper = next( - ( - node - for node in tree.body - if isinstance(node, ast.ClassDef) and node.name == "AccountReference" - ), - None, - ) - if wrapper is None: - raise RuntimeError("generated account_ref.py has no AccountReference wrapper") - - root_base = next( - ( - base - for base in wrapper.bases - if isinstance(base, ast.Subscript) - and isinstance(base.value, ast.Name) - and base.value.id == "RootModel" - ), - None, - ) - if root_base is None: - raise RuntimeError("generated AccountReference has no RootModel union base") - - def union_arm_names(node: ast.expr) -> list[str]: - if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr): - return [*union_arm_names(node.left), *union_arm_names(node.right)] - if isinstance(node, ast.Name): - return [node.id] - raise RuntimeError( - f"generated AccountReference has an unsupported union expression: {ast.unparse(node)}" - ) - - arm_names = union_arm_names(root_base.slice) - if len(arm_names) < 2 or len(set(arm_names)) != len(arm_names): - raise RuntimeError(f"generated AccountReference has invalid union arms: {arm_names!r}") - - pattern = re.compile(r"\b(account_ref(?:_\d+)?)\.AccountReference\b(?!\d)") - total_files = 0 - total_fields = 0 - - for py_file in sorted(OUTPUT_DIR.rglob("*.py")): - source = py_file.read_text() - fixed, replacements = pattern.subn( - lambda match: " | ".join(f"{match.group(1)}.{arm_name}" for arm_name in arm_names), - source, - ) - if not replacements: - continue - py_file.write_text(fixed) - total_files += 1 - total_fields += replacements - - if total_fields: - print( - f" Exposed AccountReference union arms in {total_fields} field(s) " - f"across {total_files} file(s)" - ) - else: - print(" AccountReference field annotations already expose concrete arms") - - def fix_postal_union_arm_order() -> None: """Prefer the legacy postal arm when a payload omits ``country``. @@ -2699,6 +2889,31 @@ def _field_already_widened(content: str, class_name: str, field_name: str) -> bo return "Sequence[" in content[annotation_start:annotation_end] +def _class_end_offset(content: str, class_name: str) -> int | None: + """Return the offset just past the last line of ``class_name``'s body. + + A module holds union type aliases between its classes, so "the offset of the + next ``class``" overshoots into whatever follows. The AST reports the class's + own extent. + """ + try: + tree = ast.parse(content) + except SyntaxError: + return None + node = next( + (n for n in ast.walk(tree) if isinstance(n, ast.ClassDef) and n.name == class_name), + None, + ) + if node is None or not node.end_lineno: + return None + offset = 0 + for index, line in enumerate(content.splitlines(keepends=True), start=1): + offset += len(line) + if index == node.end_lineno: + return offset + return len(content) + + def _find_class_field_block( content: str, class_name: str, field_name: str ) -> tuple[int, int] | None: @@ -3329,12 +3544,14 @@ def _require_identity_match_dimensions(self) -> TmpProviderRegistration1: validators_1 + "\n\nclass TmpProviderRegistration2(AdCPBaseModel):", 1, ) - source = source.replace( - "\n\nclass TmpProviderRegistration(RootModel[TmpProviderRegistration1 | TmpProviderRegistration2]):", - validators_2 - + "\n\nclass TmpProviderRegistration(RootModel[TmpProviderRegistration1 | TmpProviderRegistration2]):", - 1, - ) + # Arm 2 is the last class in the module, so anchor on the end + # of its own body rather than on whatever follows it. + arm_2_end = _class_end_offset(source, "TmpProviderRegistration2") + if arm_2_end is None: + raise RuntimeError( + "generated provider_registration.py has no TmpProviderRegistration2" + ) + source = source[:arm_2_end] + validators_2 + source[arm_2_end:] provider_registration.write_text(source) print(" trusted_match/provider_registration.py: added runtime validators") else: @@ -3814,6 +4031,10 @@ def fix_product_publisher_property_model_coercion() -> None: print(" core/product.py pydantic import shape not found") return + # The method annotates ``Any``; the generated module imports it only when a + # class body needs it. + source = _ensure_typing_import(source, "Any") + method = """ @model_validator(mode='before') @@ -4621,17 +4842,30 @@ def _generated_class_name(schema_rel: Path) -> str: except SyntaxError: return fallback class_names = [node.name for node in tree.body if isinstance(node, ast.ClassDef)] - if not class_names: + # A composing schema root is emitted as a module-level union alias + # rather than a class, and the alias is what a field references. + alias_names = [ + target.id + for node in tree.body + if isinstance(node, ast.Assign) + for target in node.targets + if isinstance(target, ast.Name) + ] + [ + node.target.id + for node in tree.body + if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name) + ] + if not class_names and not alias_names: return fallback normalized_fallback = fallback.lower() - for name in class_names: + for name in (*alias_names, *class_names): if name.lower() == normalized_fallback: return name stem_fallback = _pascal(schema_rel.stem).lower() - for name in class_names: + for name in (*alias_names, *class_names): if name.lower() == stem_fallback: return name - return class_names[-1] + return class_names[-1] if class_names else alias_names[-1] def _safe_import_alias(module_stem: str, used: set[str]) -> str: base = module_stem.replace("-", "_") @@ -6403,8 +6637,9 @@ def enforce_change_term_runtime_constraints() -> None: start = source.find(marker) if start < 0: continue - next_class = source.find("\n\nclass ", start + len(marker)) - end = len(source) if next_class < 0 else next_class + end = _class_end_offset(source, class_name) + if end is None: + continue block = source[start:end] if "def _require_portable_bound" in block: continue @@ -6489,7 +6724,7 @@ def _validate_change_term_set(self) -> CommercialTerms: for term in self.change_terms: if term.constraints is None: continue - constraint = term.constraints.root + constraint = term.constraints if constraint.kind == 'budget': money_fields = ( constraint.max_delta_amount, @@ -6555,15 +6790,15 @@ def main(argv: list[str] | None = None): fix_typed_additional_properties, fix_deprecated_rootmodel_fields, fix_constr_type_annotations, + fix_postal_union_arm_order, + fix_postal_country_system_pairing, + fix_beta3_adagents_renderer_constraints, unwrap_rootmodel_unions, add_rootmodel_getattr_proxy, fix_list_field_shadowing, rewrite_response_list_to_sequence, fix_reuse_model_discriminator_bug, fix_allof_merge_field_override_conflicts, - expose_account_reference_union_fields, - fix_postal_union_arm_order, - fix_postal_country_system_pairing, fix_adagents_duplicate_aliases, restore_format_category_deprecation_shim, restore_signal_catalog_type_alias, @@ -6593,7 +6828,6 @@ def main(argv: list[str] | None = None): fix_trusted_match_runtime_validators, fix_beta3_secure_url_constraints, fix_beta3_package_request_constraints, - fix_beta3_adagents_renderer_constraints, restore_trusted_match_compatibility_aliases, fix_publisher_tmpx_mapping_key_constraints, fix_wholesale_cache_scope_defaults, diff --git a/src/adcp/decisioning/refine.py b/src/adcp/decisioning/refine.py index 3fcfa4f0a..de44c2ac0 100644 --- a/src/adcp/decisioning/refine.py +++ b/src/adcp/decisioning/refine.py @@ -156,7 +156,7 @@ def build_refinement_applied( :param refines: ``request.refine`` (length N). :param outcomes: Adopter's per-entry outcomes (must also be length N). - :returns: Wire-shape ``RefinementApplied`` (RootModel) instances (one per entry). + :returns: Wire-shape ``RefinementApplied`` instances (one per entry). :raises ValueError: ``len(outcomes) != len(refines)``. Developer-facing, not buyer-facing — adopter-side bug. """ @@ -170,7 +170,6 @@ def build_refinement_applied( ) from adcp.types import ( - RefinementApplied, RefinementApplied1, RefinementApplied2, RefinementApplied3, @@ -215,7 +214,7 @@ def build_refinement_applied( raise ValueError( f"Unknown refine scope {scope!r}; expected " "'request' | 'product' | 'proposal'." ) - out.append(RefinementApplied(root=applied)) + out.append(applied) return out diff --git a/src/adcp/feed_mirror.py b/src/adcp/feed_mirror.py index 4e9cd3f94..d609b85c9 100644 --- a/src/adcp/feed_mirror.py +++ b/src/adcp/feed_mirror.py @@ -661,7 +661,9 @@ def _event_entity(event: WholesaleFeedEvent) -> FeedEntity: def _apply_event(self, event: WholesaleFeedEvent) -> None: event_type = str(event.event_type) - payload = event.payload + # Each branch below knows its arm from event_type, which is a string + # rather than the union's discriminator, so the payload stays untyped. + payload: Any = event.payload if event_type in ("product.created", "product.updated"): product = getattr(payload, "product", None) if product is not None: diff --git a/src/adcp/reporting_inspection.py b/src/adcp/reporting_inspection.py index 87958af30..7e0128261 100644 --- a/src/adcp/reporting_inspection.py +++ b/src/adcp/reporting_inspection.py @@ -21,8 +21,9 @@ import idna import jsonschema import rfc8785 -from pydantic import BaseModel, ValidationError +from pydantic import BaseModel, TypeAdapter, ValidationError +from adcp._deferred_adapters import deferred_adapter from adcp.reporting import ReportingInspectionContext, ReportingObservation from adcp.signing._bounded_http import ResponseTooLargeError, async_read_limited_bytes from adcp.signing._idna_canonicalize import canonicalize_host @@ -36,6 +37,13 @@ ReportingReportDefinition, ) +# ReportingControlTotal is a union, so validation goes through an adapter. + + +@deferred_adapter +def _control_total_adapter() -> TypeAdapter[ReportingControlTotal]: + return TypeAdapter(ReportingControlTotal) + class ReportingInspectionCode(str, Enum): RESOURCE_UNAVAILABLE = "RESOURCE_UNAVAILABLE" @@ -542,7 +550,7 @@ def _default_control_totals( try: payload = target.model_dump(mode="json", exclude_none=True) payload["value"] = _decimal_string(value, str(target.value_type)) - totals.append(ReportingControlTotal.model_validate(payload)) + totals.append(_control_total_adapter().validate_python(payload)) except (ValueError, ValidationError) as error: raise ReportingInspectionError( ReportingInspectionCode.CONTROL_TOTAL_MISMATCH, diff --git a/src/adcp/server/principal.py b/src/adcp/server/principal.py index 541cf125d..0f3f132f2 100644 --- a/src/adcp/server/principal.py +++ b/src/adcp/server/principal.py @@ -19,8 +19,9 @@ from urllib.parse import urlsplit, urlunsplit from uuid import uuid4 -from pydantic import BaseModel +from pydantic import BaseModel, TypeAdapter +from adcp._deferred_adapters import deferred_adapter from adcp.server.idempotency import canonical_json_sha256 from adcp.types import ( AgentDeclarations, @@ -37,6 +38,13 @@ SyncPrincipalResponse, ) +# AgentReportingDestination is a union, so validation goes through an adapter. + + +@deferred_adapter +def _reporting_destination_adapter() -> TypeAdapter[AgentReportingDestination]: + return TypeAdapter(AgentReportingDestination) + @dataclass(frozen=True) class PrincipalIdentity: @@ -299,7 +307,7 @@ def _normalize_destination(destination: AgentReportingDestination) -> AgentRepor recipient["identity"] = _normalize_coordinate( identity, field="reporting_destinations.recipient.identity" ) - return AgentReportingDestination.model_validate(payload) + return _reporting_destination_adapter().validate_python(payload) class PrincipalService: diff --git a/src/adcp/types/_forward_compat.py b/src/adcp/types/_forward_compat.py index 672a45152..5b33c2f82 100644 --- a/src/adcp/types/_forward_compat.py +++ b/src/adcp/types/_forward_compat.py @@ -73,7 +73,6 @@ from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( PublisherDomain as BundledPublisherDomain, ) -from adcp.types.generated_poc.core.async_response_data import AdcpAsyncResponseData from adcp.types.generated_poc.core.canonical_format_kind import CanonicalFormatKind from adcp.types.generated_poc.core.canonical_product import PublisherDomain from adcp.types.generated_poc.core.creative_manifest import CreativeManifest @@ -498,9 +497,9 @@ def _apply_forward_compat() -> None: _patch_model_field(response, "offers", GenericAlias(list, _ReadbackOffer)) response.model_rebuild(force=True) - # These eager wrappers captured build/preview validators before the patches. - # Refresh both levels so completed task callbacks retain typed manifests. - AdcpAsyncResponseData.model_rebuild(force=True) + # This eager wrapper captured build/preview validators before the patches. + # Rebuilding it re-resolves AdcpAsyncResponseData, a union type alias, so + # completed task callbacks retain typed manifests. McpWebhookPayload.model_rebuild(force=True) _patch_model_field(Format, "assets", list[FormatAssetUnion] | None) diff --git a/src/adcp/types/error_narrowing.py b/src/adcp/types/error_narrowing.py index e96d8ed55..c7cda8eb0 100644 --- a/src/adcp/types/error_narrowing.py +++ b/src/adcp/types/error_narrowing.py @@ -97,6 +97,22 @@ def _split_at_variant( _MAX_NARROW_INPUT_SIZE = 500 +def _asset_variant_index(loc: tuple[Any, ...]) -> int | None: + """Index of the asset-variant arm in ``loc``, or ``None``. + + Pydantic spells that arm by the wrapper class name when the union is + wrapped (``"AssetVariant"``) and by its members when the union is inline + (``"tagged-union[ImageAsset,VideoAsset,...]"``). Both name the same arm, + and an error under it beats the sibling open-asset arm. + """ + for index, segment in enumerate(loc): + if segment == "AssetVariant" or ( + isinstance(segment, str) and segment.startswith("tagged-union[") + ): + return index + return None + + def narrow_union_errors( errors: Any, ) -> list[Any]: @@ -154,8 +170,9 @@ def narrow_union_errors( for err in errors_list: loc = tuple(err.get("loc", ())) - if "AssetVariant" in loc: - asset_variant_prefixes.add(loc[: loc.index("AssetVariant")]) + index = _asset_variant_index(loc) + if index is not None: + asset_variant_prefixes.add(loc[:index]) for err in errors_list: loc = tuple(err.get("loc", ())) @@ -169,7 +186,12 @@ def narrow_union_errors( buckets.setdefault(prefix, []).append((variant, err)) if not buckets: - return errors_list + # Nothing to narrow by variant name. Return what survived the + # asset-variant drop above, which is already the focused set; an + # inline tagged union spells its arm in a form no variant-name + # heuristic recognizes, and falling back to the input here would put + # the dropped sibling arm back. + return [dict(err) for err in passthrough] if passthrough else errors_list # Defensive copy of the dicts we're about to surface — the caller # might mutate the returned list and we don't want that to leak diff --git a/src/adcp/types/generated_poc/a2ui/bound_value.py b/src/adcp/types/generated_poc/a2ui/bound_value.py index 8a75fb5f5..af7994071 100644 --- a/src/adcp/types/generated_poc/a2ui/bound_value.py +++ b/src/adcp/types/generated_poc/a2ui/bound_value.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: a2ui/bound_value.json -# timestamp: 2026-06-01T00:32:59+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated +from typing import Annotated from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field class A2UiBoundValue1(AdCPBaseModel): @@ -49,20 +49,10 @@ class A2UiBoundValue5(AdCPBaseModel): path: str -class A2UiBoundValue( - RootModel[ - A2UiBoundValue1 | A2UiBoundValue2 | A2UiBoundValue3 | A2UiBoundValue4 | A2UiBoundValue5 - ] -): - root: Annotated[ - A2UiBoundValue1 | A2UiBoundValue2 | A2UiBoundValue3 | A2UiBoundValue4 | A2UiBoundValue5, - Field( - description='A value that can be a literal or bound to a path in the data model', - title='A2UI Bound Value', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +A2UiBoundValue = Annotated[ + A2UiBoundValue1 | A2UiBoundValue2 | A2UiBoundValue3 | A2UiBoundValue4 | A2UiBoundValue5, + Field( + description='A value that can be a literal or bound to a path in the data model', + title='A2UI Bound Value', + ), +] diff --git a/src/adcp/types/generated_poc/account/get_account_financials_request.py b/src/adcp/types/generated_poc/account/get_account_financials_request.py index fcc1185de..b1fd73023 100644 --- a/src/adcp/types/generated_poc/account/get_account_financials_request.py +++ b/src/adcp/types/generated_poc/account/get_account_financials_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: account/get_account_financials_request.json -# timestamp: 2026-06-05T16:54:33+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -20,7 +20,7 @@ class GetAccountFinancialsRequest(AdcpVersionEnvelope): extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2, + account_ref.AccountReference, Field(description='Account to query financials for. Must be an operator-billed account.'), ] period: Annotated[ diff --git a/src/adcp/types/generated_poc/account/list_account_changes_request.py b/src/adcp/types/generated_poc/account/list_account_changes_request.py index 7175245fb..1b96713e3 100644 --- a/src/adcp/types/generated_poc/account/list_account_changes_request.py +++ b/src/adcp/types/generated_poc/account/list_account_changes_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: account/list_account_changes_request.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -47,7 +47,7 @@ class ListAccountChangesRequest(AdCPBaseModel): ), ] = None account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2, + account_ref.AccountReference, Field( description='Account whose change feed to read. The resolved account and returned cursor are bound to the authenticated principal.' ), diff --git a/src/adcp/types/generated_poc/account/list_accounts_request.py b/src/adcp/types/generated_poc/account/list_accounts_request.py index bbdfe0d5b..f65123b30 100644 --- a/src/adcp/types/generated_poc/account/list_accounts_request.py +++ b/src/adcp/types/generated_poc/account/list_accounts_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: account/list_accounts_request.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -30,7 +30,7 @@ class ListAccountsRequest(AdcpVersionEnvelope): extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Optional exact account filter. Use `account_id` to retrieve one known seller/storefront account, or the complete natural key (`brand` + `operator` + optional `operator_unit`, fixed `currency`, buyer-selected account `timezone`, and `sandbox`) for buyer-declared accounts. When present, the seller returns only matching accounts visible to the authenticated caller.' ), diff --git a/src/adcp/types/generated_poc/account/report_usage_request.py b/src/adcp/types/generated_poc/account/report_usage_request.py index aab96bddd..bfa65dd3c 100644 --- a/src/adcp/types/generated_poc/account/report_usage_request.py +++ b/src/adcp/types/generated_poc/account/report_usage_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: account/report_usage_request.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -21,7 +21,7 @@ class UsageItem(AdCPBaseModel): extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2, Field(description='Account for this usage record.') + account_ref.AccountReference, Field(description='Account for this usage record.') ] media_buy_id: Annotated[ str | None, diff --git a/src/adcp/types/generated_poc/account/sync_accounts_request.py b/src/adcp/types/generated_poc/account/sync_accounts_request.py index 20b06c3be..049dbab0c 100644 --- a/src/adcp/types/generated_poc/account/sync_accounts_request.py +++ b/src/adcp/types/generated_poc/account/sync_accounts_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: account/sync_accounts_request.json -# timestamp: 2026-09-01T19:37:00+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -27,7 +27,7 @@ class Accounts(AdCPBaseModel): extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Settings-update key. When present, this entry targets an existing account by `account_id` (seller-owned account namespace) or natural key (buyer-declared account settings-update against a previously-provisioned account). Mutually exclusive with the flat `brand` + `operator` + `billing` provisioning trio. When `account` is present, the seller MUST NOT create a new account — entries that would otherwise trigger provisioning are rejected with `UNSUPPORTED_PROVISIONING`.' ), @@ -135,7 +135,7 @@ class Accounts1(AdCPBaseModel): extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2, + account_ref.AccountReference, Field( description='Settings-update key. When present, this entry targets an existing account by `account_id` (seller-owned account namespace) or natural key (buyer-declared account settings-update against a previously-provisioned account). Mutually exclusive with the flat `brand` + `operator` + `billing` provisioning trio. When `account` is present, the seller MUST NOT create a new account — entries that would otherwise trigger provisioning are rejected with `UNSUPPORTED_PROVISIONING`.' ), diff --git a/src/adcp/types/generated_poc/account/sync_governance_request.py b/src/adcp/types/generated_poc/account/sync_governance_request.py index d292fd5f5..0d12a1069 100644 --- a/src/adcp/types/generated_poc/account/sync_governance_request.py +++ b/src/adcp/types/generated_poc/account/sync_governance_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: account/sync_governance_request.json -# timestamp: 2026-08-28T20:03:29+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -48,7 +48,7 @@ class Account(AdCPBaseModel): extra='forbid', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2, + account_ref.AccountReference, Field( description='Account to sync governance agents for. Use account_id for account-id namespaces or brand + operator for buyer-declared accounts.' ), diff --git a/src/adcp/types/generated_poc/account/sync_governance_response.py b/src/adcp/types/generated_poc/account/sync_governance_response.py index 785cd8e32..8ad40d311 100644 --- a/src/adcp/types/generated_poc/account/sync_governance_response.py +++ b/src/adcp/types/generated_poc/account/sync_governance_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: account/sync_governance_response.json -# timestamp: 2026-09-05T19:08:34+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -63,7 +63,7 @@ class Account(AdCPBaseModel): extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2, Field(description='Account reference, echoed from request') + account_ref.AccountReference, Field(description='Account reference, echoed from request') ] status: Annotated[ Status, diff --git a/src/adcp/types/generated_poc/adagents.py b/src/adcp/types/generated_poc/adagents.py index 8b63a031a..4c9655bbe 100644 --- a/src/adcp/types/generated_poc/adagents.py +++ b/src/adcp/types/generated_poc/adagents.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: adagents.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -655,71 +655,105 @@ class AuthorizedAgents3(AuthorizedAgentBaseFields): ] = None -class AuthorizedAgents( - RootModel[ - AuthorizedAgents1 - | AuthorizedAgents2 - | AuthorizedAgents3 - | AuthorizedAgents4 - | AuthorizedAgents5 - | AuthorizedAgents6 - ] -): - root: Annotated[ - AuthorizedAgents1 - | AuthorizedAgents2 - | AuthorizedAgents3 - | AuthorizedAgents4 - | AuthorizedAgents5 - | AuthorizedAgents6, - Field(discriminator='authorization_type'), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AuthorizedAgents = Annotated[ + AuthorizedAgents1 + | AuthorizedAgents2 + | AuthorizedAgents3 + | AuthorizedAgents4 + | AuthorizedAgents5 + | AuthorizedAgents6, + Field(discriminator='authorization_type'), +] + class AuthorizedAgents10(AuthorizedAgents3): authorization_type: Literal['inline_properties'] = 'inline_properties' -AuthorizedAgents7 = AuthorizedAgents +AuthorizedAgents7 = Annotated[ + AuthorizedAgents8 + | AuthorizedAgents9 + | AuthorizedAgents10 + | AuthorizedAgents11 + | AuthorizedAgents12 + | AuthorizedAgents13, + Field(discriminator='authorization_type'), +] class AuthorizedAgents17(AuthorizedAgents3): authorization_type: Literal['inline_properties'] = 'inline_properties' -AuthorizedAgents14 = AuthorizedAgents +AuthorizedAgents14 = Annotated[ + AuthorizedAgents15 + | AuthorizedAgents16 + | AuthorizedAgents17 + | AuthorizedAgents18 + | AuthorizedAgents19 + | AuthorizedAgents20, + Field(discriminator='authorization_type'), +] class AuthorizedAgents24(AuthorizedAgents3): authorization_type: Literal['inline_properties'] = 'inline_properties' -AuthorizedAgents21 = AuthorizedAgents +AuthorizedAgents21 = Annotated[ + AuthorizedAgents22 + | AuthorizedAgents23 + | AuthorizedAgents24 + | AuthorizedAgents25 + | AuthorizedAgents26 + | AuthorizedAgents27, + Field(discriminator='authorization_type'), +] class AuthorizedAgents31(AuthorizedAgents3): authorization_type: Literal['inline_properties'] = 'inline_properties' -AuthorizedAgents28 = AuthorizedAgents +AuthorizedAgents28 = Annotated[ + AuthorizedAgents29 + | AuthorizedAgents30 + | AuthorizedAgents31 + | AuthorizedAgents32 + | AuthorizedAgents33 + | AuthorizedAgents34, + Field(discriminator='authorization_type'), +] class AuthorizedAgents38(AuthorizedAgents3): authorization_type: Literal['inline_properties'] = 'inline_properties' -AuthorizedAgents35 = AuthorizedAgents +AuthorizedAgents35 = Annotated[ + AuthorizedAgents36 + | AuthorizedAgents37 + | AuthorizedAgents38 + | AuthorizedAgents39 + | AuthorizedAgents40 + | AuthorizedAgents41, + Field(discriminator='authorization_type'), +] class AuthorizedAgents45(AuthorizedAgents3): authorization_type: Literal['inline_properties'] = 'inline_properties' -AuthorizedAgents42 = AuthorizedAgents +AuthorizedAgents42 = Annotated[ + AuthorizedAgents43 + | AuthorizedAgents44 + | AuthorizedAgents45 + | AuthorizedAgents46 + | AuthorizedAgents47 + | AuthorizedAgents48, + Field(discriminator='authorization_type'), +] class AdcpAgentsAuthorization27(AdCPBaseModel): @@ -1336,6 +1370,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class AdcpAgentsAuthorization(RootModel[AdcpAgentsAuthorization1 | AdcpAgentsAuthorization2]): root: Annotated[ AdcpAgentsAuthorization1 | AdcpAgentsAuthorization2, @@ -1689,11 +1725,6 @@ class AdcpAgentsAuthorization(RootModel[AdcpAgentsAuthorization1 | AdcpAgentsAut title='AdCP Agents Authorization', ), ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) @model_validator(mode='before') @classmethod @@ -1723,3 +1754,8 @@ def _validate_reference_renderer_catalog(cls, value: Any) -> Any: 'format_revision must equal reference_renderer.format_revision' ) return value + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) diff --git a/src/adcp/types/generated_poc/brand/acquire_rights_request.py b/src/adcp/types/generated_poc/brand/acquire_rights_request.py index fb0007c74..bc0cae3a3 100644 --- a/src/adcp/types/generated_poc/brand/acquire_rights_request.py +++ b/src/adcp/types/generated_poc/brand/acquire_rights_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: brand/acquire_rights_request.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -96,7 +96,7 @@ class AcquireRightsRequest(AdcpVersionEnvelope): ] buyer: Annotated[brand_ref.BrandReference, Field(description="The buyer's brand identity")] account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account context for this acquisition. Used by the brand agent to resolve any governance agent previously bound for this brand+operator pair via sync_governance. When both an inline governance_context token and a bound governance agent are present, the token is verified against that resolved relationship. An agent advertising adcp.governance_enforcement for acquire_rights MUST resolve an account and returns ACCOUNT_REQUIRED when neither the request nor an existing resource supplies one; it MUST NOT infer that a missing token means ungoverned. Legacy non-claiming agents may continue to treat omission of both fields as ungoverned during 3.x. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts.' ), diff --git a/src/adcp/types/generated_poc/brand/creative_approval_response.py b/src/adcp/types/generated_poc/brand/creative_approval_response.py index 23b1ee0ec..7593c775e 100644 --- a/src/adcp/types/generated_poc/brand/creative_approval_response.py +++ b/src/adcp/types/generated_poc/brand/creative_approval_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: brand/creative_approval_response.json -# timestamp: 2026-09-05T19:08:34+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations @@ -94,9 +94,13 @@ class CreativeApprovalResponse4(AdcpVersionEnvelope, ProtocolEnvelope): ext: ext_1.ExtensionObject | None = None -CreativeApprovalResponse = ( +CreativeApprovalResponse = Annotated[ CreativeApprovalResponse1 | CreativeApprovalResponse2 | CreativeApprovalResponse3 - | CreativeApprovalResponse4 -) + | CreativeApprovalResponse4, + Field( + description='Response from the approval_webhook after reviewing a submitted creative. Uses discriminated union on `approval_status` field: approved, rejected, or pending_review.', + title='Creative Approval Response', + ), +] diff --git a/src/adcp/types/generated_poc/brand/update_rights_request.py b/src/adcp/types/generated_poc/brand/update_rights_request.py index 3bd865690..8836044a1 100644 --- a/src/adcp/types/generated_poc/brand/update_rights_request.py +++ b/src/adcp/types/generated_poc/brand/update_rights_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: brand/update_rights_request.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -33,7 +33,7 @@ class UpdateRightsRequest(AdcpVersionEnvelope): str, Field(description='Rights grant identifier from acquire_rights response') ] account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account context for this update. Used by the brand agent to resolve any governance agent previously bound for this brand+operator pair via sync_governance — update_rights is a modification-phase governance trigger (per `/docs/governance/campaign/specification#spend-commit-invocation`) and the brand agent consults the bound agent when computing the incremental commit delta. When both an inline governance_context token (on the protocol envelope) and a bound governance agent are present, the inline token wins. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. The estimated_impressions / commit-delta projection rule for governance-aware updates is tracked separately and not yet normative on this task.' ), diff --git a/src/adcp/types/generated_poc/brand/verify_brand_claims_request.py b/src/adcp/types/generated_poc/brand/verify_brand_claims_request.py index 85219467e..2f1805d63 100644 --- a/src/adcp/types/generated_poc/brand/verify_brand_claims_request.py +++ b/src/adcp/types/generated_poc/brand/verify_brand_claims_request.py @@ -1,15 +1,15 @@ # generated by datamodel-codegen: # filename: brand/verify_brand_claims_request.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field, RootModel +from pydantic import AwareDatetime, ConfigDict, Field from ..core.version_envelope import AdcpVersionEnvelope from ..enums import property_type @@ -135,19 +135,14 @@ class ClaimEntry4(AdCPBaseModel): claim: Claim3 -class ClaimEntry(RootModel[ClaimEntry1 | ClaimEntry2 | ClaimEntry3 | ClaimEntry4]): - root: Annotated[ - ClaimEntry1 | ClaimEntry2 | ClaimEntry3 | ClaimEntry4, - Field( - description='One verification claim. Shape mirrors the single-target `verify_brand_claim` request body (minus the version envelope, which lives on the batch).', - discriminator='claim_type', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +ClaimEntry = Annotated[ + ClaimEntry1 | ClaimEntry2 | ClaimEntry3 | ClaimEntry4, + Field( + description='One verification claim. Shape mirrors the single-target `verify_brand_claim` request body (minus the version envelope, which lives on the batch).', + discriminator='claim_type', + ), +] + class VerifyBrandClaimsRequestBulk(AdcpVersionEnvelope): model_config = ConfigDict( diff --git a/src/adcp/types/generated_poc/brand_discovery.py b/src/adcp/types/generated_poc/brand_discovery.py index 76d63d49f..870d9e52e 100644 --- a/src/adcp/types/generated_poc/brand_discovery.py +++ b/src/adcp/types/generated_poc/brand_discovery.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: _brand_discovery.json -# timestamp: 2026-10-02T13:37:46+00:00 +# timestamp: 2026-10-04T00:53:30+00:00 from __future__ import annotations @@ -207,6 +207,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class LocalizedStringListEntry(RootModel[dict[str, list[str]]]): root: Annotated[dict[str, list[str]], Field(max_length=1, min_length=1)] @@ -234,6 +236,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class LocaleTag(ScalarStr): __slots__ = () _constraints = { @@ -303,6 +307,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class Kind(StrEnum): name = 'name' value = 'value' @@ -395,6 +401,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class GuidelineSeverity(StrEnum): must = 'must' should = 'should' @@ -500,6 +508,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class Fonts(AdCPBaseModel): __pydantic_extra__: Dict[str, FontRole] model_config = ConfigDict( @@ -2735,485 +2745,475 @@ class BrandDiscovery5(Brand): last_updated: AwareDatetime | None = None -class BrandDiscovery( - RootModel[ - BrandDiscovery1 | BrandDiscovery2 | BrandDiscovery3 | BrandDiscovery4 | BrandDiscovery5 - ] -): - root: Annotated[ - BrandDiscovery1 | BrandDiscovery2 | BrandDiscovery3 | BrandDiscovery4 | BrandDiscovery5, - Field( - description='Brand identity and discovery file. Hosted at /.well-known/brand.json on house domains. Contains the full brand portfolio with identity, creative assets, and digital properties. Brands are identified by house + brand_id (like properties are identified by publisher + property_id). Supports variants: house portfolio (full brand data), brand agent (agent provides brand info via MCP), house redirect (pointer to house domain), or authoritative location redirect.', - examples=[ - { - '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', - 'authoritative_location': 'https://adcontextprotocol.org/brand/abc123/brand.json', - }, - { - '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', - 'house': 'nikeinc.com', - 'note': 'Redirect to house domain for full brand portfolio', - }, - { - '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', - 'version': '1.0', +BrandDiscovery = Annotated[ + BrandDiscovery1 | BrandDiscovery2 | BrandDiscovery3 | BrandDiscovery4 | BrandDiscovery5, + Field( + description='Brand identity and discovery file. Hosted at /.well-known/brand.json on house domains. Contains the full brand portfolio with identity, creative assets, and digital properties. Brands are identified by house + brand_id (like properties are identified by publisher + property_id). Supports variants: house portfolio (full brand data), brand agent (agent provides brand info via MCP), house redirect (pointer to house domain), or authoritative location redirect.', + examples=[ + { + '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', + 'authoritative_location': 'https://adcontextprotocol.org/brand/abc123/brand.json', + }, + { + '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', + 'house': 'nikeinc.com', + 'note': 'Redirect to house domain for full brand portfolio', + }, + { + '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', + 'version': '1.0', + 'agents': [ + {'type': 'brand', 'url': 'https://agent.acme.com/mcp', 'id': 'acme_brand'} + ], + }, + { + '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', + 'version': '1.0', + 'house': { + 'domain': 'pg.com', + 'name': 'Procter & Gamble', + 'architecture': 'house_of_brands', 'agents': [ - {'type': 'brand', 'url': 'https://agent.acme.com/mcp', 'id': 'acme_brand'} + { + 'type': 'governance', + 'url': 'https://agents.pg.com/governance', + 'id': 'pg_governance', + 'description': 'Brand safety and compliance for all P&G brands', + 'jwks_uri': 'https://agents.pg.com/.well-known/jwks.json', + } ], }, - { - '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', - 'version': '1.0', - 'house': { - 'domain': 'pg.com', - 'name': 'Procter & Gamble', - 'architecture': 'house_of_brands', - 'agents': [ + 'brands': [ + { + 'id': 'tide', + 'url': 'https://tide.com', + 'names': [{'en_US': 'Tide'}, {'es_MX': 'Tide'}, {'zh_CN': '汰渍'}], + 'keller_type': 'master', + 'industries': ['cpg'], + 'description': 'Laundry detergent brand', + 'logos': [ { - 'type': 'governance', - 'url': 'https://agents.pg.com/governance', - 'id': 'pg_governance', - 'description': 'Brand safety and compliance for all P&G brands', - 'jwks_uri': 'https://agents.pg.com/.well-known/jwks.json', - } - ], - }, - 'brands': [ - { - 'id': 'tide', - 'url': 'https://tide.com', - 'names': [{'en_US': 'Tide'}, {'es_MX': 'Tide'}, {'zh_CN': '汰渍'}], - 'keller_type': 'master', - 'industries': ['cpg'], - 'description': 'Laundry detergent brand', - 'logos': [ - { - 'url': 'https://cdn.pg.com/tide/logo-square.png', - 'orientation': 'square', - 'background': 'transparent-bg', - 'variant': 'primary', - 'usage': 'Primary logo for general use', - }, - { - 'url': 'https://cdn.pg.com/tide/logo-horizontal-dark.png', - 'orientation': 'horizontal', - 'background': 'dark-bg', - 'variant': 'full-lockup', - 'usage': 'Full lockup for dark backgrounds', - }, - ], - 'colors': { - 'primary': '#FF6600', - 'secondary': '#0066CC', - 'background': '#FFFFFF', - 'text': '#1A1A1A', - 'heading': '#FF6600', - 'body': '#333333', - 'label': '#666666', - 'border': '#E5E5E5', - 'divider': '#F0F0F0', - 'surface_1': '#F9F9F9', - 'surface_2': '#EFEFEF', + 'url': 'https://cdn.pg.com/tide/logo-square.png', + 'orientation': 'square', + 'background': 'transparent-bg', + 'variant': 'primary', + 'usage': 'Primary logo for general use', }, - 'tone': { - 'voice': 'clean, fresh, trustworthy', - 'attributes': ['reliable', 'family-friendly', 'confident'], - 'dos': ['Use simple, direct language', 'Emphasize cleaning power'], - 'donts': ['Avoid technical jargon', "Don't be overly serious"], + { + 'url': 'https://cdn.pg.com/tide/logo-horizontal-dark.png', + 'orientation': 'horizontal', + 'background': 'dark-bg', + 'variant': 'full-lockup', + 'usage': 'Full lockup for dark backgrounds', }, - 'tagline': [{'en_US': "Tide's In, Dirt's Out"}], - 'visual_guidelines': { - 'photography': { - 'realism': 'natural', - 'lighting': 'soft daylight', - 'color_temperature': 'warm', - 'contrast': 'medium', - 'depth_of_field': 'medium', - 'subject': { - 'people': { - 'age_range': '25-45', - 'diversity': 'mixed', - 'mood': ['confident', 'relaxed', 'happy'], - }, - 'product_focus': 'in-use', - 'setting': 'indoor', - }, - 'framing': { - 'subject_position': 'center', - 'crop_style': 'waist-up', - 'perspective': 'eye-level', - }, - 'preferred_aspect_ratios': ['16:9', '4:5', '1:1'], - }, - 'graphic_style': { - 'style_type': 'flat_illustration', - 'stroke_style': 'rounded', - 'stroke_weight': '2px', - 'corner_radius': '12px', - }, - 'shapes': { - 'primary_shape': 'circle', - 'secondary_shapes': ['rounded_rectangle'], - 'usage': {'max_per_layout': 2, 'overlap_allowed': False}, - }, - 'iconography': { - 'style': 'outline', - 'stroke_weight': '2px', - 'corner_style': 'rounded', - 'usage': {'max_per_frame': 3, 'size_ratio': '1:8'}, - }, - 'composition': { - 'overlays': { - 'gradient_style': 'linear', - 'gradient_direction': '180deg', - 'opacity': '60%', - }, - 'texture': {'style': 'none'}, - 'backgrounds': { - 'types_allowed': [ - 'solid_color', - 'gradient', - 'blurred_photo', - ] - }, - }, - 'border_radius': { - 'none': '0', - 'default': '12px', - 'small': '4px', - 'large': '20px', - 'pill': '999px', - }, - 'elevation': { - 'none': 'none', - 'subtle': '0 1px 3px rgba(0,0,0,0.08)', - 'card': '0 4px 8px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.06)', - 'modal': '0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.06)', - }, - 'spacing': { - 'unit': '8px', - 'scale': { - 'xs': '4px', - 'sm': '8px', - 'md': '16px', - 'lg': '24px', - 'xl': '32px', - '2xl': '48px', + ], + 'colors': { + 'primary': '#FF6600', + 'secondary': '#0066CC', + 'background': '#FFFFFF', + 'text': '#1A1A1A', + 'heading': '#FF6600', + 'body': '#333333', + 'label': '#666666', + 'border': '#E5E5E5', + 'divider': '#F0F0F0', + 'surface_1': '#F9F9F9', + 'surface_2': '#EFEFEF', + }, + 'tone': { + 'voice': 'clean, fresh, trustworthy', + 'attributes': ['reliable', 'family-friendly', 'confident'], + 'dos': ['Use simple, direct language', 'Emphasize cleaning power'], + 'donts': ['Avoid technical jargon', "Don't be overly serious"], + }, + 'tagline': [{'en_US': "Tide's In, Dirt's Out"}], + 'visual_guidelines': { + 'photography': { + 'realism': 'natural', + 'lighting': 'soft daylight', + 'color_temperature': 'warm', + 'contrast': 'medium', + 'depth_of_field': 'medium', + 'subject': { + 'people': { + 'age_range': '25-45', + 'diversity': 'mixed', + 'mood': ['confident', 'relaxed', 'happy'], }, + 'product_focus': 'in-use', + 'setting': 'indoor', }, - 'motion': { - 'transition_style': 'dissolve', - 'animation_speed': 'moderate', - 'easing': 'ease-in-out', - 'text_entrance': 'fade', - 'pacing': 'moderate', - 'kinetic_typography': False, + 'framing': { + 'subject_position': 'center', + 'crop_style': 'waist-up', + 'perspective': 'eye-level', }, - 'logo_placement': { - 'preferred_position': 'bottom-right', - 'min_clear_space': '0.5x', - 'min_height': '32px', - 'background_contrast': 'any', + 'preferred_aspect_ratios': ['16:9', '4:5', '1:1'], + }, + 'graphic_style': { + 'style_type': 'flat_illustration', + 'stroke_style': 'rounded', + 'stroke_weight': '2px', + 'corner_radius': '12px', + }, + 'shapes': { + 'primary_shape': 'circle', + 'secondary_shapes': ['rounded_rectangle'], + 'usage': {'max_per_layout': 2, 'overlap_allowed': False}, + }, + 'iconography': { + 'style': 'outline', + 'stroke_weight': '2px', + 'corner_style': 'rounded', + 'usage': {'max_per_frame': 3, 'size_ratio': '1:8'}, + }, + 'composition': { + 'overlays': { + 'gradient_style': 'linear', + 'gradient_direction': '180deg', + 'opacity': '60%', }, - 'colorways': [ - { - 'name': 'primary', - 'foreground': '#FFFFFF', - 'background': '#FF6600', - 'accent': '#0066CC', - }, - { - 'name': 'inverted', - 'foreground': '#FF6600', - 'background': '#FFFFFF', - 'accent': '#0066CC', - 'border': '#FF6600', - }, - ], - 'type_scale': { - 'base_width': '1080px', - 'heading': { - 'font': 'primary', - 'size': '48px', - 'weight': '700', - 'line_height': '1.1', - 'text_transform': 'none', - }, - 'subheading': { - 'font': 'primary', - 'size': '24px', - 'weight': '600', - 'line_height': '1.3', - }, - 'body': { - 'font': 'secondary', - 'size': '16px', - 'weight': '400', - 'line_height': '1.5', - }, - 'cta': { - 'font': 'primary', - 'size': '18px', - 'weight': '700', - 'text_transform': 'uppercase', - 'letter_spacing': '0.05em', - }, + 'texture': {'style': 'none'}, + 'backgrounds': { + 'types_allowed': [ + 'solid_color', + 'gradient', + 'blurred_photo', + ] }, - 'restrictions': [ - 'Never place text over the product', - 'Do not use black backgrounds', - 'No stock photography of people on phones', - ], }, - 'properties': [ - {'type': 'website', 'identifier': 'tide.com', 'primary': True}, - { - 'type': 'mobile_app', - 'store': 'apple', - 'identifier': 'com.pg.tide', + 'border_radius': { + 'none': '0', + 'default': '12px', + 'small': '4px', + 'large': '20px', + 'pill': '999px', + }, + 'elevation': { + 'none': 'none', + 'subtle': '0 1px 3px rgba(0,0,0,0.08)', + 'card': '0 4px 8px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.06)', + 'modal': '0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.06)', + }, + 'spacing': { + 'unit': '8px', + 'scale': { + 'xs': '4px', + 'sm': '8px', + 'md': '16px', + 'lg': '24px', + 'xl': '32px', + '2xl': '48px', }, - ], - 'contact': {'email': 'brands@pg.com'}, - }, - { - 'id': 'pampers', - 'url': 'https://pampers.com', - 'names': [{'en_US': 'Pampers'}], - 'keller_type': 'master', - 'industries': ['cpg'], - 'logos': [ - { - 'url': 'https://cdn.pg.com/pampers/logo.png', - 'orientation': 'horizontal', - 'variant': 'primary', - } - ], - 'colors': {'primary': '#00A0D2'}, - 'properties': [ - {'type': 'website', 'identifier': 'pampers.com', 'primary': True} - ], - }, - ], - 'contact': {'name': 'P&G Brand Team', 'email': 'brands@pg.com'}, - 'last_updated': '2026-01-15T10:00:00Z', - }, - { - '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', - 'version': '1.0', - 'house': { - 'domain': 'nikeinc.com', - 'name': 'Nike, Inc.', - 'architecture': 'hybrid', - }, - 'brands': [ - { - 'id': 'nike', - 'url': 'https://nike.com', - 'names': [{'en_US': 'Nike'}, {'zh_CN': '耐克'}, {'ja_JP': 'ナイキ'}], - 'keller_type': 'master', - 'logos': [ + }, + 'motion': { + 'transition_style': 'dissolve', + 'animation_speed': 'moderate', + 'easing': 'ease-in-out', + 'text_entrance': 'fade', + 'pacing': 'moderate', + 'kinetic_typography': False, + }, + 'logo_placement': { + 'preferred_position': 'bottom-right', + 'min_clear_space': '0.5x', + 'min_height': '32px', + 'background_contrast': 'any', + }, + 'colorways': [ { - 'url': 'https://cdn.nike.com/swoosh-dark.svg', - 'orientation': 'horizontal', - 'background': 'dark-bg', - 'variant': 'icon', - 'usage': 'Swoosh icon for dark backgrounds', + 'name': 'primary', + 'foreground': '#FFFFFF', + 'background': '#FF6600', + 'accent': '#0066CC', }, { - 'url': 'https://cdn.nike.com/logo-full.svg', - 'orientation': 'horizontal', - 'background': 'light-bg', - 'variant': 'full-lockup', - 'usage': 'Full logo with wordmark for light backgrounds', + 'name': 'inverted', + 'foreground': '#FF6600', + 'background': '#FFFFFF', + 'accent': '#0066CC', + 'border': '#FF6600', }, ], - 'colors': {'primary': '#111111', 'accent': '#FF6600'}, - 'tone': 'inspirational, bold, athletic', - 'tagline': [{'en_US': 'Just Do It'}], - 'properties': [ - {'type': 'website', 'identifier': 'nike.com', 'primary': True}, - {'type': 'website', 'identifier': 'nike.cn', 'region': 'CN'}, - { - 'type': 'mobile_app', - 'store': 'apple', - 'identifier': 'com.nike.omega', + 'type_scale': { + 'base_width': '1080px', + 'heading': { + 'font': 'primary', + 'size': '48px', + 'weight': '700', + 'line_height': '1.1', + 'text_transform': 'none', }, - ], - }, - { - 'id': 'air_jordan', - 'url': 'https://jordan.com', - 'names': [ - {'en_US': 'Air Jordan'}, - {'en_US': 'Jordan'}, - {'en_US': 'Jumpman'}, - ], - 'keller_type': 'endorsed', - 'parent_brand': 'nike', - 'logos': [ - { - 'url': 'https://cdn.nike.com/jumpman.svg', - 'orientation': 'square', - 'background': 'transparent-bg', - 'variant': 'icon', - } - ], - 'colors': {'primary': '#CE1141', 'secondary': '#111111'}, - 'properties': [ - {'type': 'website', 'identifier': 'jordan.com', 'primary': True}, - {'type': 'website', 'identifier': 'jumpman23.com'}, - ], - 'brand_agent': {'url': 'https://dam.nike.com/mcp', 'id': 'nike_dam'}, - }, - { - 'id': 'converse', - 'url': 'https://converse.com', - 'names': [{'en_US': 'Converse'}], - 'keller_type': 'independent', - 'logos': [ - { - 'url': 'https://cdn.converse.com/star.svg', - 'orientation': 'square', - 'variant': 'icon', - } - ], - 'properties': [ - {'type': 'website', 'identifier': 'converse.com', 'primary': True} - ], - }, - ], - 'authorized_operators': [ - { - 'domain': 'wpp.com', - 'brands': ['nike', 'air_jordan'], - 'countries': ['US', 'GB', 'DE', 'FR'], - 'scopes': ['media_buying', 'creative_generation'], - 'valid_from': '2026-01-01T00:00:00Z', - 'valid_until': '2027-01-01T00:00:00Z', - }, - { - 'domain': 'dentsu.co.jp', - 'brands': ['nike'], - 'countries': ['JP'], - 'scopes': ['media_buying'], - }, - {'domain': 'nike.com', 'brands': ['*'], 'scopes': ['all']}, - ], - 'last_updated': '2026-01-15T10:00:00Z', - }, - { - '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', - 'version': '1.0', - 'house': { - 'domain': 'mediavine.com', - 'name': 'Mediavine', - 'architecture': 'branded_house', - }, - 'brands': [ - { - 'id': 'mediavine', - 'url': 'https://mediavine.com', - 'names': [{'en_US': 'Mediavine'}], - 'keller_type': 'master', - 'properties': [ - {'type': 'website', 'identifier': 'mediavine.com', 'primary': True}, - { - 'type': 'website', - 'identifier': 'thehollywoodgossip.com', - 'relationship': 'delegated', + 'subheading': { + 'font': 'primary', + 'size': '24px', + 'weight': '600', + 'line_height': '1.3', }, - { - 'type': 'website', - 'identifier': 'foodfanatic.com', - 'relationship': 'delegated', + 'body': { + 'font': 'secondary', + 'size': '16px', + 'weight': '400', + 'line_height': '1.5', }, - { - 'type': 'website', - 'identifier': 'thebiglead.com', - 'relationship': 'delegated', + 'cta': { + 'font': 'primary', + 'size': '18px', + 'weight': '700', + 'text_transform': 'uppercase', + 'letter_spacing': '0.05em', }, + }, + 'restrictions': [ + 'Never place text over the product', + 'Do not use black backgrounds', + 'No stock photography of people on phones', ], - 'agents': [ - { - 'type': 'sales', - 'url': 'https://ads.mediavine.com/mcp', - 'id': 'mediavine_sales', - } - ], - } - ], - 'last_updated': '2026-01-15T10:00:00Z', - }, - { - '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', - 'version': '1.0', - 'house': { - 'domain': 'nikeinc.com', - 'name': 'Nike, Inc.', - 'architecture': 'hybrid', - }, - 'brands': [ - { - 'id': 'nike_sb', - 'names': [{'en_US': 'Nike SB'}], - 'keller_type': 'sub_brand', - 'logos': [ - {'url': 'https://nike.com/sb/logo.svg', 'variant': 'primary'} - ], - } - ], - 'brand_refs': [ - {'domain': 'converse.com', 'brand_id': 'converse'}, - {'domain': 'jordan.com', 'brand_id': 'jordan'}, - ], - 'last_updated': '2026-01-15T10:00:00Z', - }, - { - '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', - 'version': '1.0', - 'house': {'domain': 'wpp.com', 'name': 'WPP plc'}, - 'brand_refs': [ - { - 'domain': 'bbh-sport.com', - 'brand_id': 'bbh_sport', - 'managed_by': 'bbh.com', - }, - { - 'domain': 'ogilvy-toyota.com', - 'brand_id': 'ogilvy_toyota', - 'managed_by': 'ogilvy.com', }, - {'domain': 'wpp-direct.com', 'brand_id': 'wpp_direct'}, - ], - 'last_updated': '2026-01-15T10:00:00Z', + 'properties': [ + {'type': 'website', 'identifier': 'tide.com', 'primary': True}, + { + 'type': 'mobile_app', + 'store': 'apple', + 'identifier': 'com.pg.tide', + }, + ], + 'contact': {'email': 'brands@pg.com'}, + }, + { + 'id': 'pampers', + 'url': 'https://pampers.com', + 'names': [{'en_US': 'Pampers'}], + 'keller_type': 'master', + 'industries': ['cpg'], + 'logos': [ + { + 'url': 'https://cdn.pg.com/pampers/logo.png', + 'orientation': 'horizontal', + 'variant': 'primary', + } + ], + 'colors': {'primary': '#00A0D2'}, + 'properties': [ + {'type': 'website', 'identifier': 'pampers.com', 'primary': True} + ], + }, + ], + 'contact': {'name': 'P&G Brand Team', 'email': 'brands@pg.com'}, + 'last_updated': '2026-01-15T10:00:00Z', + }, + { + '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', + 'version': '1.0', + 'house': { + 'domain': 'nikeinc.com', + 'name': 'Nike, Inc.', + 'architecture': 'hybrid', }, - { - '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', - 'version': '1.0', - 'id': 'converse', - 'names': [{'en_US': 'Converse'}], - 'keller_type': 'sub_brand', - 'house_domain': 'nikeinc.com', - 'logos': [{'url': 'https://converse.com/logo.svg', 'variant': 'primary'}], - 'tagline': 'Sneaker for the streets', - 'last_updated': '2026-01-15T10:00:00Z', + 'brands': [ + { + 'id': 'nike', + 'url': 'https://nike.com', + 'names': [{'en_US': 'Nike'}, {'zh_CN': '耐克'}, {'ja_JP': 'ナイキ'}], + 'keller_type': 'master', + 'logos': [ + { + 'url': 'https://cdn.nike.com/swoosh-dark.svg', + 'orientation': 'horizontal', + 'background': 'dark-bg', + 'variant': 'icon', + 'usage': 'Swoosh icon for dark backgrounds', + }, + { + 'url': 'https://cdn.nike.com/logo-full.svg', + 'orientation': 'horizontal', + 'background': 'light-bg', + 'variant': 'full-lockup', + 'usage': 'Full logo with wordmark for light backgrounds', + }, + ], + 'colors': {'primary': '#111111', 'accent': '#FF6600'}, + 'tone': 'inspirational, bold, athletic', + 'tagline': [{'en_US': 'Just Do It'}], + 'properties': [ + {'type': 'website', 'identifier': 'nike.com', 'primary': True}, + {'type': 'website', 'identifier': 'nike.cn', 'region': 'CN'}, + { + 'type': 'mobile_app', + 'store': 'apple', + 'identifier': 'com.nike.omega', + }, + ], + }, + { + 'id': 'air_jordan', + 'url': 'https://jordan.com', + 'names': [ + {'en_US': 'Air Jordan'}, + {'en_US': 'Jordan'}, + {'en_US': 'Jumpman'}, + ], + 'keller_type': 'endorsed', + 'parent_brand': 'nike', + 'logos': [ + { + 'url': 'https://cdn.nike.com/jumpman.svg', + 'orientation': 'square', + 'background': 'transparent-bg', + 'variant': 'icon', + } + ], + 'colors': {'primary': '#CE1141', 'secondary': '#111111'}, + 'properties': [ + {'type': 'website', 'identifier': 'jordan.com', 'primary': True}, + {'type': 'website', 'identifier': 'jumpman23.com'}, + ], + 'brand_agent': {'url': 'https://dam.nike.com/mcp', 'id': 'nike_dam'}, + }, + { + 'id': 'converse', + 'url': 'https://converse.com', + 'names': [{'en_US': 'Converse'}], + 'keller_type': 'independent', + 'logos': [ + { + 'url': 'https://cdn.converse.com/star.svg', + 'orientation': 'square', + 'variant': 'icon', + } + ], + 'properties': [ + {'type': 'website', 'identifier': 'converse.com', 'primary': True} + ], + }, + ], + 'authorized_operators': [ + { + 'domain': 'wpp.com', + 'brands': ['nike', 'air_jordan'], + 'countries': ['US', 'GB', 'DE', 'FR'], + 'scopes': ['media_buying', 'creative_generation'], + 'valid_from': '2026-01-01T00:00:00Z', + 'valid_until': '2027-01-01T00:00:00Z', + }, + { + 'domain': 'dentsu.co.jp', + 'brands': ['nike'], + 'countries': ['JP'], + 'scopes': ['media_buying'], + }, + {'domain': 'nike.com', 'brands': ['*'], 'scopes': ['all']}, + ], + 'last_updated': '2026-01-15T10:00:00Z', + }, + { + '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', + 'version': '1.0', + 'house': { + 'domain': 'mediavine.com', + 'name': 'Mediavine', + 'architecture': 'branded_house', }, - { - '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', - 'version': '1.0', - 'id': 'patagonia', - 'names': [{'en_US': 'Patagonia'}], - 'keller_type': 'independent', - 'logos': [{'url': 'https://patagonia.com/logo.svg', 'variant': 'primary'}], - 'tagline': "We're in business to save our home planet.", - 'last_updated': '2026-01-15T10:00:00Z', + 'brands': [ + { + 'id': 'mediavine', + 'url': 'https://mediavine.com', + 'names': [{'en_US': 'Mediavine'}], + 'keller_type': 'master', + 'properties': [ + {'type': 'website', 'identifier': 'mediavine.com', 'primary': True}, + { + 'type': 'website', + 'identifier': 'thehollywoodgossip.com', + 'relationship': 'delegated', + }, + { + 'type': 'website', + 'identifier': 'foodfanatic.com', + 'relationship': 'delegated', + }, + { + 'type': 'website', + 'identifier': 'thebiglead.com', + 'relationship': 'delegated', + }, + ], + 'agents': [ + { + 'type': 'sales', + 'url': 'https://ads.mediavine.com/mcp', + 'id': 'mediavine_sales', + } + ], + } + ], + 'last_updated': '2026-01-15T10:00:00Z', + }, + { + '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', + 'version': '1.0', + 'house': { + 'domain': 'nikeinc.com', + 'name': 'Nike, Inc.', + 'architecture': 'hybrid', }, - ], - title='Brand Discovery', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) + 'brands': [ + { + 'id': 'nike_sb', + 'names': [{'en_US': 'Nike SB'}], + 'keller_type': 'sub_brand', + 'logos': [ + {'url': 'https://nike.com/sb/logo.svg', 'variant': 'primary'} + ], + } + ], + 'brand_refs': [ + {'domain': 'converse.com', 'brand_id': 'converse'}, + {'domain': 'jordan.com', 'brand_id': 'jordan'}, + ], + 'last_updated': '2026-01-15T10:00:00Z', + }, + { + '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', + 'version': '1.0', + 'house': {'domain': 'wpp.com', 'name': 'WPP plc'}, + 'brand_refs': [ + { + 'domain': 'bbh-sport.com', + 'brand_id': 'bbh_sport', + 'managed_by': 'bbh.com', + }, + { + 'domain': 'ogilvy-toyota.com', + 'brand_id': 'ogilvy_toyota', + 'managed_by': 'ogilvy.com', + }, + {'domain': 'wpp-direct.com', 'brand_id': 'wpp_direct'}, + ], + 'last_updated': '2026-01-15T10:00:00Z', + }, + { + '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', + 'version': '1.0', + 'id': 'converse', + 'names': [{'en_US': 'Converse'}], + 'keller_type': 'sub_brand', + 'house_domain': 'nikeinc.com', + 'logos': [{'url': 'https://converse.com/logo.svg', 'variant': 'primary'}], + 'tagline': 'Sneaker for the streets', + 'last_updated': '2026-01-15T10:00:00Z', + }, + { + '$schema': 'https://adcontextprotocol.org/schemas/3.2.1/brand.json', + 'version': '1.0', + 'id': 'patagonia', + 'names': [{'en_US': 'Patagonia'}], + 'keller_type': 'independent', + 'logos': [{'url': 'https://patagonia.com/logo.svg', 'variant': 'primary'}], + 'tagline': "We're in business to save our home planet.", + 'last_updated': '2026-01-15T10:00:00Z', + }, + ], + title='Brand Discovery', + ), +] diff --git a/src/adcp/types/generated_poc/bundled/protocol/get_adcp_capabilities_response.py b/src/adcp/types/generated_poc/bundled/protocol/get_adcp_capabilities_response.py index a527f51fb..f43a515e2 100644 --- a/src/adcp/types/generated_poc/bundled/protocol/get_adcp_capabilities_response.py +++ b/src/adcp/types/generated_poc/bundled/protocol/get_adcp_capabilities_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: bundled/protocol/get_adcp_capabilities_response.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -645,18 +645,11 @@ class Tasks19(AdCPBaseModel): modes: Annotated[list[Literal['signed_context']], Field(max_length=1, min_length=1)] -class Tasks10( - RootModel[Tasks | Tasks12 | Tasks13 | Tasks14 | Tasks15 | Tasks16 | Tasks17 | Tasks18 | Tasks19] -): - root: Annotated[ - Tasks | Tasks12 | Tasks13 | Tasks14 | Tasks15 | Tasks16 | Tasks17 | Tasks18 | Tasks19, - Field(discriminator='task'), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +Tasks10 = Annotated[ + Tasks | Tasks12 | Tasks13 | Tasks14 | Tasks15 | Tasks16 | Tasks17 | Tasks18 | Tasks19, + Field(discriminator='task'), +] + class AnyOf(AdCPBaseModel): model_config = ConfigDict( @@ -711,13 +704,11 @@ class AnyOf5(AdCPBaseModel): ] -class AnyOf3(RootModel[AnyOf | AnyOf5]): - root: Annotated[AnyOf | AnyOf5, Field(discriminator='kind')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AnyOf3 = Annotated[ + AnyOf | AnyOf5, + Field(discriminator='kind'), +] + class AcceptedGovernanceAgents(AdCPBaseModel): model_config = ConfigDict( diff --git a/src/adcp/types/generated_poc/collection/base_collection_source.py b/src/adcp/types/generated_poc/collection/base_collection_source.py index 3d9e36469..fbc4c3806 100644 --- a/src/adcp/types/generated_poc/collection/base_collection_source.py +++ b/src/adcp/types/generated_poc/collection/base_collection_source.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: collection/base_collection_source.json -# timestamp: 2026-09-01T19:37:00+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..enums import distribution_identifier_type from ..enums import genre_taxonomy as genre_taxonomy_1 @@ -97,19 +97,11 @@ class BaseCollectionSource3(AdCPBaseModel): ] -class BaseCollectionSource( - RootModel[BaseCollectionSource1 | BaseCollectionSource2 | BaseCollectionSource3] -): - root: Annotated[ - BaseCollectionSource1 | BaseCollectionSource2 | BaseCollectionSource3, - Field( - description='A source of collections for a collection list. Supports three selection patterns: distribution identifiers (cross-publisher), publisher-specific collection IDs, or publisher-specific genres.', - discriminator='selection_type', - title='Base Collection Source', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +BaseCollectionSource = Annotated[ + BaseCollectionSource1 | BaseCollectionSource2 | BaseCollectionSource3, + Field( + description='A source of collections for a collection list. Supports three selection patterns: distribution identifiers (cross-publisher), publisher-specific collection IDs, or publisher-specific genres.', + discriminator='selection_type', + title='Base Collection Source', + ), +] diff --git a/src/adcp/types/generated_poc/collection/collection_list.py b/src/adcp/types/generated_poc/collection/collection_list.py index b86c17ee2..e3923b83d 100644 --- a/src/adcp/types/generated_poc/collection/collection_list.py +++ b/src/adcp/types/generated_poc/collection/collection_list.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: collection/collection_list.json -# timestamp: 2026-05-02T19:36:29+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -23,7 +23,7 @@ class CollectionList(AdCPBaseModel): None ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account that owns this list. Returned as account_id form (seller-assigned identifier).' ), diff --git a/src/adcp/types/generated_poc/collection/create_collection_list_request.py b/src/adcp/types/generated_poc/collection/create_collection_list_request.py index 0f45c3083..7b3411883 100644 --- a/src/adcp/types/generated_poc/collection/create_collection_list_request.py +++ b/src/adcp/types/generated_poc/collection/create_collection_list_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: collection/create_collection_list_request.json -# timestamp: 2026-05-28T10:34:10+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -20,7 +20,7 @@ class CreateCollectionListRequest(AdcpVersionEnvelope): extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account that will own the list. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. When omitted, this task applies its task-local single-account shortcut: if exactly one account is accessible to the authenticated caller, the seller may assign the list to that account; otherwise it MUST return an account-required or ambiguous-account error. Omission MUST NOT mean an undocumented credential-local default account.' ), diff --git a/src/adcp/types/generated_poc/collection/delete_collection_list_request.py b/src/adcp/types/generated_poc/collection/delete_collection_list_request.py index a641efe18..4c4f726bf 100644 --- a/src/adcp/types/generated_poc/collection/delete_collection_list_request.py +++ b/src/adcp/types/generated_poc/collection/delete_collection_list_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: collection/delete_collection_list_request.json -# timestamp: 2026-05-22T13:15:12+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -20,7 +20,7 @@ class DeleteCollectionListRequest(AdcpVersionEnvelope): ) list_id: Annotated[str, Field(description='ID of the collection list to delete')] account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.' ), diff --git a/src/adcp/types/generated_poc/collection/get_collection_list_request.py b/src/adcp/types/generated_poc/collection/get_collection_list_request.py index 84cd47164..6c007fb8c 100644 --- a/src/adcp/types/generated_poc/collection/get_collection_list_request.py +++ b/src/adcp/types/generated_poc/collection/get_collection_list_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: collection/get_collection_list_request.json -# timestamp: 2026-05-22T13:15:12+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -35,7 +35,7 @@ class GetCollectionListRequest(AdcpVersionEnvelope): ) list_id: Annotated[str, Field(description='ID of the collection list to retrieve')] account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account that owns the list. Required when the authenticated agent has access to multiple accounts and the list_id is not globally unique within that scope; optional otherwise.' ), diff --git a/src/adcp/types/generated_poc/collection/list_collection_lists_request.py b/src/adcp/types/generated_poc/collection/list_collection_lists_request.py index c0b4a19c7..864877576 100644 --- a/src/adcp/types/generated_poc/collection/list_collection_lists_request.py +++ b/src/adcp/types/generated_poc/collection/list_collection_lists_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: collection/list_collection_lists_request.json -# timestamp: 2026-05-22T13:15:12+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -20,7 +20,7 @@ class ListCollectionListsRequest(AdcpVersionEnvelope): extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Filter to lists owned by this account. When omitted, returns lists across all accounts accessible to the authenticated agent.' ), diff --git a/src/adcp/types/generated_poc/collection/update_collection_list_request.py b/src/adcp/types/generated_poc/collection/update_collection_list_request.py index 6c98428e5..fe0561e62 100644 --- a/src/adcp/types/generated_poc/collection/update_collection_list_request.py +++ b/src/adcp/types/generated_poc/collection/update_collection_list_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: collection/update_collection_list_request.json -# timestamp: 2026-05-22T13:15:12+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -21,7 +21,7 @@ class UpdateCollectionListRequest(AdcpVersionEnvelope): ) list_id: Annotated[str, Field(description='ID of the collection list to update')] account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.' ), diff --git a/src/adcp/types/generated_poc/compliance/comply_test_controller_response.py b/src/adcp/types/generated_poc/compliance/comply_test_controller_response.py index 65852b45d..b10d28cdd 100644 --- a/src/adcp/types/generated_poc/compliance/comply_test_controller_response.py +++ b/src/adcp/types/generated_poc/compliance/comply_test_controller_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: compliance/comply_test_controller_response.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -11,7 +11,7 @@ from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field from ..core import context as context_1 from ..core import ext as ext_1 @@ -291,13 +291,11 @@ class RecordedCalls2(AdCPBaseModel): ] = None -class RecordedCalls(RootModel[RecordedCalls1 | RecordedCalls2]): - root: Annotated[RecordedCalls1 | RecordedCalls2, Field(discriminator='attestation_mode')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +RecordedCalls = Annotated[ + RecordedCalls1 | RecordedCalls2, + Field(discriminator='attestation_mode'), +] + class Error(StrEnum): INVALID_TRANSITION = 'INVALID_TRANSITION' diff --git a/src/adcp/types/generated_poc/compliance/task_completion_data.py b/src/adcp/types/generated_poc/compliance/task_completion_data.py index ef1744d32..5690b1089 100644 --- a/src/adcp/types/generated_poc/compliance/task_completion_data.py +++ b/src/adcp/types/generated_poc/compliance/task_completion_data.py @@ -1,38 +1,25 @@ # generated by datamodel-codegen: # filename: compliance/task_completion_data.json -# timestamp: 2026-09-24T08:59:48+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated +from typing import Annotated -from pydantic import Field, RootModel +from pydantic import Field from ..media_buy import create_media_buy_response, get_products_response from ..signals import get_signals_response from . import get_creative_features_completion -class ComplianceTaskCompletionData( - RootModel[ - get_products_response.GetProductsResponse - | get_signals_response.GetSignalsResponse - | create_media_buy_response.CreateMediaBuyResponse1 - | get_creative_features_completion.GetCreativeFeaturesComplianceCompletion - ] -): - root: Annotated[ - get_products_response.GetProductsResponse - | get_signals_response.GetSignalsResponse - | create_media_buy_response.CreateMediaBuyResponse1 - | get_creative_features_completion.GetCreativeFeaturesComplianceCompletion, - Field( - description="Bounded completion union for the async tasks supported by force_task_completion. Polling and production task-result resolution use the originating task's manifest response schema rather than embedding the global async union.", - title='Compliance Task Completion Data', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +ComplianceTaskCompletionData = Annotated[ + get_products_response.GetProductsResponse + | get_signals_response.GetSignalsResponse + | create_media_buy_response.CreateMediaBuyResponse1 + | get_creative_features_completion.GetCreativeFeaturesComplianceCompletion, + Field( + description="Bounded completion union for the async tasks supported by force_task_completion. Polling and production task-result resolution use the originating task's manifest response schema rather than embedding the global async union.", + title='Compliance Task Completion Data', + ), +] diff --git a/src/adcp/types/generated_poc/content_standards/artifact.py b/src/adcp/types/generated_poc/content_standards/artifact.py index 7375ab492..2b8fd6c1e 100644 --- a/src/adcp/types/generated_poc/content_standards/artifact.py +++ b/src/adcp/types/generated_poc/content_standards/artifact.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: content_standards/artifact.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-03T00:36:50+00:00 from __future__ import annotations @@ -126,6 +126,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class Assets1(AdCPBaseModel): type: Literal['text'] = 'text' role: Annotated[ @@ -237,13 +239,11 @@ class Assets4(AdCPBaseModel): ] = None -class Assets(RootModel[Assets1 | Assets2 | Assets3 | Assets4]): - root: Annotated[Assets1 | Assets2 | Assets3 | Assets4, Field(discriminator='type')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +Assets = Annotated[ + Assets1 | Assets2 | Assets3 | Assets4, + Field(discriminator='type'), +] + class Artifact(AdCPBaseModel): model_config = ConfigDict( diff --git a/src/adcp/types/generated_poc/content_standards/get_media_buy_artifacts_request.py b/src/adcp/types/generated_poc/content_standards/get_media_buy_artifacts_request.py index 5158fb573..b0fdf23d4 100644 --- a/src/adcp/types/generated_poc/content_standards/get_media_buy_artifacts_request.py +++ b/src/adcp/types/generated_poc/content_standards/get_media_buy_artifacts_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: content_standards/get_media_buy_artifacts_request.json -# timestamp: 2026-05-22T13:15:12+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -38,7 +38,7 @@ class Pagination(AdCPBaseModel): class GetMediaBuyArtifactsRequest(AdcpVersionEnvelope): account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Filter artifacts to a specific account. When omitted, returns artifacts across all accessible accounts.' ), diff --git a/src/adcp/types/generated_poc/core/account_identity_change.py b/src/adcp/types/generated_poc/core/account_identity_change.py index c377e3a7a..dea0f8d79 100644 --- a/src/adcp/types/generated_poc/core/account_identity_change.py +++ b/src/adcp/types/generated_poc/core/account_identity_change.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/account_identity_change.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, RootModel +from pydantic import AwareDatetime, ConfigDict, Field from . import operator_identity @@ -51,16 +51,10 @@ class AccountIdentityChange2(AdCPBaseModel): ] -class AccountIdentityChange(RootModel[AccountIdentityChange1 | AccountIdentityChange2]): - root: Annotated[ - AccountIdentityChange1 | AccountIdentityChange2, - Field( - description="A requested operator-identity change that has not been applied. The account's top-level operator and operator_unit remain canonical until this object disappears after approval and the canonical fields advance. Rejected changes remain visible until replaced by a later request so buyers polling list_accounts can observe the outcome.", - title='Account Identity Change', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AccountIdentityChange = Annotated[ + AccountIdentityChange1 | AccountIdentityChange2, + Field( + description="A requested operator-identity change that has not been applied. The account's top-level operator and operator_unit remain canonical until this object disappears after approval and the canonical fields advance. Rejected changes remain visible until replaced by a later request so buyers polling list_accounts can observe the outcome.", + title='Account Identity Change', + ), +] diff --git a/src/adcp/types/generated_poc/core/account_identity_change_preview.py b/src/adcp/types/generated_poc/core/account_identity_change_preview.py index 064750bf2..e614355c3 100644 --- a/src/adcp/types/generated_poc/core/account_identity_change_preview.py +++ b/src/adcp/types/generated_poc/core/account_identity_change_preview.py @@ -1,11 +1,11 @@ # generated by datamodel-codegen: # filename: core/account_identity_change_preview.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr @@ -105,24 +105,12 @@ class AccountIdentityChangePreview3(AdCPBaseModel): blockers: Annotated[list[Blocker], Field(min_length=1)] -class AccountIdentityChangePreview( - RootModel[ - AccountIdentityChangePreview1 - | AccountIdentityChangePreview2 - | AccountIdentityChangePreview3 - ] -): - root: Annotated[ - AccountIdentityChangePreview1 - | AccountIdentityChangePreview2 - | AccountIdentityChangePreview3, - Field( - description='Non-persisted dry-run result for a requested operator-identity change. Canonical account fields and revision remain unchanged.', - title='Account Identity Change Preview', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AccountIdentityChangePreview = Annotated[ + AccountIdentityChangePreview1 + | AccountIdentityChangePreview2 + | AccountIdentityChangePreview3, + Field( + description='Non-persisted dry-run result for a requested operator-identity change. Canonical account fields and revision remain unchanged.', + title='Account Identity Change Preview', + ), +] diff --git a/src/adcp/types/generated_poc/core/account_ref.py b/src/adcp/types/generated_poc/core/account_ref.py index f0c26ec8c..d578d0775 100644 --- a/src/adcp/types/generated_poc/core/account_ref.py +++ b/src/adcp/types/generated_poc/core/account_ref.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/account_ref.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated +from typing import Annotated from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from . import brand_ref from . import operator_unit as operator_unit_1 @@ -67,36 +67,30 @@ class AccountReference2(AdCPBaseModel): ] = False -class AccountReference(RootModel[AccountReference1 | AccountReference2]): - root: Annotated[ - AccountReference1 | AccountReference2, - Field( - description="Reference to an advertiser account by seller-assigned ID or natural key. account_id is the seller/storefront handle returned by list_accounts. The natural key is brand + operator + optional operator_unit.id + optional fixed currency + optional buyer-selected account timezone + sandbox; it describes the advertiser object the seller resolves or provisions. After a capability-gated identity rekey, the former natural key is tombstoned and returns ACCOUNT_MOVED to authorized callers rather than resolving or provisioning another account. brand.countries qualifies the advertiser's geographic identity. operator_unit identifies the operator-owned buying seat or business unit and is distinct from the seller account. operator_unit.name is display metadata and does not participate in identity. Use account_id when the seller or upstream platform owns the canonical account namespace; use the natural key for buyer-declared accounts (require_operator_auth: false).", - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'nova-athletics.example', 'countries': ['NL']}, - 'operator': 'nova-athletics.example', - 'operator_unit': {'id': '234284238', 'name': 'Nova EMEA'}, - 'currency': 'EUR', - 'timezone': 'Europe/Amsterdam', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AccountReference = Annotated[ + AccountReference1 | AccountReference2, + Field( + description="Reference to an advertiser account by seller-assigned ID or natural key. account_id is the seller/storefront handle returned by list_accounts. The natural key is brand + operator + optional operator_unit.id + optional fixed currency + optional buyer-selected account timezone + sandbox; it describes the advertiser object the seller resolves or provisions. After a capability-gated identity rekey, the former natural key is tombstoned and returns ACCOUNT_MOVED to authorized callers rather than resolving or provisioning another account. brand.countries qualifies the advertiser's geographic identity. operator_unit identifies the operator-owned buying seat or business unit and is distinct from the seller account. operator_unit.name is display metadata and does not participate in identity. Use account_id when the seller or upstream platform owns the canonical account namespace; use the natural key for buyer-declared accounts (require_operator_auth: false).", + examples=[ + {'account_id': 'acc_acme_001'}, + {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, + { + 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, + 'operator': 'pinnacle-media.com', + }, + { + 'brand': {'domain': 'nova-athletics.example', 'countries': ['NL']}, + 'operator': 'nova-athletics.example', + 'operator_unit': {'id': '234284238', 'name': 'Nova EMEA'}, + 'currency': 'EUR', + 'timezone': 'Europe/Amsterdam', + }, + { + 'brand': {'domain': 'acme-corp.com'}, + 'operator': 'acme-corp.com', + 'sandbox': True, + }, + ], + title='Account Reference', + ), +] diff --git a/src/adcp/types/generated_poc/core/activation_key.py b/src/adcp/types/generated_poc/core/activation_key.py index e5d28e860..f1f666888 100644 --- a/src/adcp/types/generated_poc/core/activation_key.py +++ b/src/adcp/types/generated_poc/core/activation_key.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/activation_key.json -# timestamp: 2026-06-01T00:32:59+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field class ActivationKey1(AdCPBaseModel): @@ -30,17 +30,11 @@ class ActivationKey2(AdCPBaseModel): value: Annotated[str, Field(description='The targeting parameter value')] -class ActivationKey(RootModel[ActivationKey1 | ActivationKey2]): - root: Annotated[ - ActivationKey1 | ActivationKey2, - Field( - description="Universal identifier for using a signal on a destination platform. Can be either a segment ID or a key-value pair depending on the platform's targeting mechanism.", - discriminator='type', - title='Activation Key', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +ActivationKey = Annotated[ + ActivationKey1 | ActivationKey2, + Field( + description="Universal identifier for using a signal on a destination platform. Can be either a segment ID or a key-value pair depending on the platform's targeting mechanism.", + discriminator='type', + title='Activation Key', + ), +] diff --git a/src/adcp/types/generated_poc/core/agent_reporting_destination.py b/src/adcp/types/generated_poc/core/agent_reporting_destination.py index d6fc3f072..ac5b71dea 100644 --- a/src/adcp/types/generated_poc/core/agent_reporting_destination.py +++ b/src/adcp/types/generated_poc/core/agent_reporting_destination.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/agent_reporting_destination.json -# timestamp: 2026-09-01T19:37:00+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from . import delivery_provider, delivery_recipient, reporting_verification_profile_set @@ -202,18 +202,10 @@ class AgentReportingDestination3(AdCPBaseModel): ) -class AgentReportingDestination( - RootModel[AgentReportingDestination1 | AgentReportingDestination2 | AgentReportingDestination3] -): - root: Annotated[ - AgentReportingDestination1 | AgentReportingDestination2 | AgentReportingDestination3, - Field( - description="Reusable, non-secret reporting destination owned by the authenticated caller's relationship with one seller. Exactly one delivery pattern per destination; pattern is the discriminator and matches the managed reporting delivery pattern vocabulary. It does not grant account authority: account/reporting configuration separately binds authorized data to the seller-issued destination_ref. Sellers key ownership to the stable transport principal, never a signing key, token, or request-body identity. Credentials, private keys, bearer profiles, signed URLs, and embedded passwords are forbidden. Sellers implementing this schema advertise protocol.principal.", - title='Agent Reporting Destination', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AgentReportingDestination = Annotated[ + AgentReportingDestination1 | AgentReportingDestination2 | AgentReportingDestination3, + Field( + description="Reusable, non-secret reporting destination owned by the authenticated caller's relationship with one seller. Exactly one delivery pattern per destination; pattern is the discriminator and matches the managed reporting delivery pattern vocabulary. It does not grant account authority: account/reporting configuration separately binds authorized data to the seller-issued destination_ref. Sellers key ownership to the stable transport principal, never a signing key, token, or request-body identity. Credentials, private keys, bearer profiles, signed URLs, and embedded passwords are forbidden. Sellers implementing this schema advertise protocol.principal.", + title='Agent Reporting Destination', + ), +] diff --git a/src/adcp/types/generated_poc/core/assets/asset_union.py b/src/adcp/types/generated_poc/core/assets/asset_union.py index cf6ed495a..055ebd6c3 100644 --- a/src/adcp/types/generated_poc/core/assets/asset_union.py +++ b/src/adcp/types/generated_poc/core/assets/asset_union.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/assets/asset_union.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -283,19 +283,14 @@ class MacroBearingUrl2(ScalarStr): } -class MacroBearingUrl(RootModel[str | MacroBearingUrl1 | MacroBearingUrl2]): - root: Annotated[ - str | MacroBearingUrl1 | MacroBearingUrl2, - Field( - description='Backward-compatible URL value that preserves the existing `uri-template` branch (including deep links, protocol-relative references, and RFC 6570 host variables) while additionally accepting absolute HTTP(S) URLs with byte-preserved macro delimiters not legal in a strict URI, including `%%...%%`, `[...]`, and `${...}`. Macro-bearing HTTP(S) URLs require a valid lexical authority and valid percent triplets; an AdCP verifier replaces each recognized token with an unreserved sentinel and validates the resulting absolute URI. Token semantics are established only by `macro_declarations`.', - title='Macro-bearing URL', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +MacroBearingUrl = Annotated[ + str | MacroBearingUrl1 | MacroBearingUrl2, + Field( + description='Backward-compatible URL value that preserves the existing `uri-template` branch (including deep links, protocol-relative references, and RFC 6570 host variables) while additionally accepting absolute HTTP(S) URLs with byte-preserved macro delimiters not legal in a strict URI, including `%%...%%`, `[...]`, and `${...}`. Macro-bearing HTTP(S) URLs require a valid lexical authority and valid percent triplets; an AdCP verifier replaces each recognized token with an unreserved sentinel and validates the resulting absolute URI. Token semantics are established only by `macro_declarations`.', + title='Macro-bearing URL', + ), +] + class VastVersion(StrEnum): field_2_0 = '2.0' @@ -1845,6 +1840,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class MarkdownAsset(AdCPBaseModel): model_config = ConfigDict( extra='allow', @@ -2142,6 +2139,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class ImageAsset(AdCPBaseModel): model_config = ConfigDict( extra='allow', @@ -2491,6 +2490,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class MacroDeclarationModel(AdCPBaseModel): model_config = ConfigDict( extra='forbid', @@ -2937,6 +2938,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class MacroDeclaration5(MacroDeclarationModel): location: Location6 | None = None @@ -3219,61 +3222,31 @@ class DaastTrackerAsset(AdCPBaseModel): ] = None -class AssetVariant( - RootModel[ - ImageAsset - | VideoAsset - | AudioAsset - | VastAsset - | DisplayTagAsset - | TextAsset - | UrlAsset - | HtmlAsset - | JavascriptAsset - | ZipAsset - | WebhookAsset - | CssAsset - | DaastAsset - | MarkdownAsset - | BriefAsset - | CatalogAsset - | PublishedPostAsset - | CardAsset - | PixelTrackerAsset - | VastTrackerAsset - | DaastTrackerAsset - ] -): - root: Annotated[ - ImageAsset - | VideoAsset - | AudioAsset - | VastAsset - | DisplayTagAsset - | TextAsset - | UrlAsset - | HtmlAsset - | JavascriptAsset - | ZipAsset - | WebhookAsset - | CssAsset - | DaastAsset - | MarkdownAsset - | BriefAsset - | CatalogAsset - | PublishedPostAsset - | CardAsset - | PixelTrackerAsset - | VastTrackerAsset - | DaastTrackerAsset, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AssetVariant = Annotated[ + ImageAsset + | VideoAsset + | AudioAsset + | VastAsset + | DisplayTagAsset + | TextAsset + | UrlAsset + | HtmlAsset + | JavascriptAsset + | ZipAsset + | WebhookAsset + | CssAsset + | DaastAsset + | MarkdownAsset + | BriefAsset + | CatalogAsset + | PublishedPostAsset + | CardAsset + | PixelTrackerAsset + | VastTrackerAsset + | DaastTrackerAsset, + Field( + description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', + discriminator='asset_type', + title='AssetVariant', + ), +] diff --git a/src/adcp/types/generated_poc/core/async_response_data.py b/src/adcp/types/generated_poc/core/async_response_data.py index 7666ad0ff..e76ca6936 100644 --- a/src/adcp/types/generated_poc/core/async_response_data.py +++ b/src/adcp/types/generated_poc/core/async_response_data.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/async_response_data.json -# timestamp: 2026-09-24T08:59:48+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated +from typing import Annotated -from pydantic import Field, RootModel +from pydantic import Field from ..creative import ( get_creative_features_async_response_submitted, @@ -56,102 +56,51 @@ from . import compact_task_input_required, compact_task_submitted, compact_task_working -class AdcpAsyncResponseData( - RootModel[ - get_products_response.GetProductsResponse - | get_products_rejected.GetProductsRejected - | get_products_async_response_working.GetProductsWorking - | get_products_async_response_input_required.GetProductsInputRequired - | get_products_async_response_submitted.GetProductsSubmitted - | request_proposals_response.RequestProposalsResponse - | request_proposals_async_response_submitted.RequestProposalsSubmitted - | refine_proposals_response.RefineProposalsResponse - | refine_proposals_async_response_submitted.RefineProposalsSubmitted - | decline_proposals_response.DeclineProposalsResponse - | media_buy_commitment_response.MediaBuyCommitmentResponse - | control_media_buy_response.ControlMediaBuyResponse - | compact_task_submitted.CompactTaskSubmitted - | compact_task_working.CompactTaskWorking - | compact_task_input_required.CompactTaskInputRequired - | get_signals_response.GetSignalsResponse - | get_signals_async_response_working.GetSignalsWorking - | get_signals_async_response_submitted.GetSignalsSubmitted - | create_media_buy_response.CreateMediaBuyResponse - | create_media_buy_async_response_working.CreateMediaBuyWorking - | create_media_buy_async_response_input_required.CreateMediaBuyInputRequired - | create_media_buy_async_response_submitted.CreateMediaBuySubmitted - | update_media_buy_response.UpdateMediaBuyResponse - | update_media_buy_async_response_working.UpdateMediaBuyWorking - | update_media_buy_async_response_input_required.UpdateMediaBuyInputRequired - | update_media_buy_async_response_submitted.UpdateMediaBuySubmitted - | media_buy_delivery_webhook_result.MediaBuyDeliveryWebhookResult - | build_creative_response.BuildCreativeResponse - | preview_creative_response.PreviewCreativeResponse - | build_creative_async_response_working.BuildCreativeWorking - | build_creative_async_response_input_required.BuildCreativeInputRequired - | build_creative_async_response_submitted.BuildCreativeSubmitted - | get_creative_features_response.GetCreativeFeaturesResponse - | get_creative_features_async_response_submitted.GetCreativeFeaturesSubmitted - | sync_creatives_response.SyncCreativesResponse - | sync_creatives_async_response_working.SyncCreativesWorking - | sync_creatives_async_response_input_required.SyncCreativesInputRequired - | sync_creatives_async_response_submitted.SyncCreativesSubmitted - | sync_catalogs_response.SyncCatalogsResponse - | sync_catalogs_async_response_working.SyncCatalogsWorking - | sync_catalogs_async_response_input_required.SyncCatalogsInputRequired - | sync_catalogs_async_response_submitted.SyncCatalogsSubmitted - ] -): - root: Annotated[ - get_products_response.GetProductsResponse - | get_products_rejected.GetProductsRejected - | get_products_async_response_working.GetProductsWorking - | get_products_async_response_input_required.GetProductsInputRequired - | get_products_async_response_submitted.GetProductsSubmitted - | request_proposals_response.RequestProposalsResponse - | request_proposals_async_response_submitted.RequestProposalsSubmitted - | refine_proposals_response.RefineProposalsResponse - | refine_proposals_async_response_submitted.RefineProposalsSubmitted - | decline_proposals_response.DeclineProposalsResponse - | media_buy_commitment_response.MediaBuyCommitmentResponse - | control_media_buy_response.ControlMediaBuyResponse - | compact_task_submitted.CompactTaskSubmitted - | compact_task_working.CompactTaskWorking - | compact_task_input_required.CompactTaskInputRequired - | get_signals_response.GetSignalsResponse - | get_signals_async_response_working.GetSignalsWorking - | get_signals_async_response_submitted.GetSignalsSubmitted - | create_media_buy_response.CreateMediaBuyResponse - | create_media_buy_async_response_working.CreateMediaBuyWorking - | create_media_buy_async_response_input_required.CreateMediaBuyInputRequired - | create_media_buy_async_response_submitted.CreateMediaBuySubmitted - | update_media_buy_response.UpdateMediaBuyResponse - | update_media_buy_async_response_working.UpdateMediaBuyWorking - | update_media_buy_async_response_input_required.UpdateMediaBuyInputRequired - | update_media_buy_async_response_submitted.UpdateMediaBuySubmitted - | media_buy_delivery_webhook_result.MediaBuyDeliveryWebhookResult - | build_creative_response.BuildCreativeResponse - | preview_creative_response.PreviewCreativeResponse - | build_creative_async_response_working.BuildCreativeWorking - | build_creative_async_response_input_required.BuildCreativeInputRequired - | build_creative_async_response_submitted.BuildCreativeSubmitted - | get_creative_features_response.GetCreativeFeaturesResponse - | get_creative_features_async_response_submitted.GetCreativeFeaturesSubmitted - | sync_creatives_response.SyncCreativesResponse - | sync_creatives_async_response_working.SyncCreativesWorking - | sync_creatives_async_response_input_required.SyncCreativesInputRequired - | sync_creatives_async_response_submitted.SyncCreativesSubmitted - | sync_catalogs_response.SyncCatalogsResponse - | sync_catalogs_async_response_working.SyncCatalogsWorking - | sync_catalogs_async_response_input_required.SyncCatalogsInputRequired - | sync_catalogs_async_response_submitted.SyncCatalogsSubmitted, - Field( - description="Validation union of supported async webhook payloads. For completed/failed statuses, use the main task response schema. For rejected get_products outcomes and working/input-required/submitted statuses, use the status-specific schemas. Because this shared webhook union is not tagged with the originating task type, callers MUST also validate a terminal result against that task or event's specific schema. Polling responses use a generic result selected through manifest.task_result_resolution instead of embedding this union.", - title='AdCP Async Response Data', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AdcpAsyncResponseData = Annotated[ + get_products_response.GetProductsResponse + | get_products_rejected.GetProductsRejected + | get_products_async_response_working.GetProductsWorking + | get_products_async_response_input_required.GetProductsInputRequired + | get_products_async_response_submitted.GetProductsSubmitted + | request_proposals_response.RequestProposalsResponse + | request_proposals_async_response_submitted.RequestProposalsSubmitted + | refine_proposals_response.RefineProposalsResponse + | refine_proposals_async_response_submitted.RefineProposalsSubmitted + | decline_proposals_response.DeclineProposalsResponse + | media_buy_commitment_response.MediaBuyCommitmentResponse + | control_media_buy_response.ControlMediaBuyResponse + | compact_task_submitted.CompactTaskSubmitted + | compact_task_working.CompactTaskWorking + | compact_task_input_required.CompactTaskInputRequired + | get_signals_response.GetSignalsResponse + | get_signals_async_response_working.GetSignalsWorking + | get_signals_async_response_submitted.GetSignalsSubmitted + | create_media_buy_response.CreateMediaBuyResponse + | create_media_buy_async_response_working.CreateMediaBuyWorking + | create_media_buy_async_response_input_required.CreateMediaBuyInputRequired + | create_media_buy_async_response_submitted.CreateMediaBuySubmitted + | update_media_buy_response.UpdateMediaBuyResponse + | update_media_buy_async_response_working.UpdateMediaBuyWorking + | update_media_buy_async_response_input_required.UpdateMediaBuyInputRequired + | update_media_buy_async_response_submitted.UpdateMediaBuySubmitted + | media_buy_delivery_webhook_result.MediaBuyDeliveryWebhookResult + | build_creative_response.BuildCreativeResponse + | preview_creative_response.PreviewCreativeResponse + | build_creative_async_response_working.BuildCreativeWorking + | build_creative_async_response_input_required.BuildCreativeInputRequired + | build_creative_async_response_submitted.BuildCreativeSubmitted + | get_creative_features_response.GetCreativeFeaturesResponse + | get_creative_features_async_response_submitted.GetCreativeFeaturesSubmitted + | sync_creatives_response.SyncCreativesResponse + | sync_creatives_async_response_working.SyncCreativesWorking + | sync_creatives_async_response_input_required.SyncCreativesInputRequired + | sync_creatives_async_response_submitted.SyncCreativesSubmitted + | sync_catalogs_response.SyncCatalogsResponse + | sync_catalogs_async_response_working.SyncCatalogsWorking + | sync_catalogs_async_response_input_required.SyncCatalogsInputRequired + | sync_catalogs_async_response_submitted.SyncCatalogsSubmitted, + Field( + description="Validation union of supported async webhook payloads. For completed/failed statuses, use the main task response schema. For rejected get_products outcomes and working/input-required/submitted statuses, use the status-specific schemas. Because this shared webhook union is not tagged with the originating task type, callers MUST also validate a terminal result against that task or event's specific schema. Polling responses use a generic result selected through manifest.task_result_resolution instead of embedding this union.", + title='AdCP Async Response Data', + ), +] diff --git a/src/adcp/types/generated_poc/core/attestation_issuer.py b/src/adcp/types/generated_poc/core/attestation_issuer.py index ffd6e9fe8..f1e8bd57b 100644 --- a/src/adcp/types/generated_poc/core/attestation_issuer.py +++ b/src/adcp/types/generated_poc/core/attestation_issuer.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/attestation_issuer.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel +from pydantic import AnyUrl, ConfigDict, Field from . import brand_ref from . import ext as ext_1 @@ -62,25 +62,19 @@ class AttestationIssuer1(AdCPBaseModel): ext: ext_1.ExtensionObject | None = None -class AttestationIssuer(RootModel[AttestationIssuer1 | AttestationIssuer2 | AttestationIssuer3]): - root: Annotated[ - AttestationIssuer1 | AttestationIssuer2 | AttestationIssuer3, - Field( - description='Canonical identity of the party that issued an attestation credential. The discriminator selects an existing AdCP identity when one exists and falls back to an HTTPS origin for other attestors. This identity is a claim carried by the presentation; evaluators MUST match it against their configured trust policy and verify it from the resolved or embedded credential before relying on it.', - discriminator='type', - examples=[ - { - 'type': 'brand', - 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, - }, - {'type': 'agent', 'agent_url': 'https://attestor.example/adcp'}, - {'type': 'origin', 'origin': 'https://credentials.example'}, - ], - title='Attestation Issuer', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AttestationIssuer = Annotated[ + AttestationIssuer1 | AttestationIssuer2 | AttestationIssuer3, + Field( + description='Canonical identity of the party that issued an attestation credential. The discriminator selects an existing AdCP identity when one exists and falls back to an HTTPS origin for other attestors. This identity is a claim carried by the presentation; evaluators MUST match it against their configured trust policy and verify it from the resolved or embedded credential before relying on it.', + discriminator='type', + examples=[ + { + 'type': 'brand', + 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, + }, + {'type': 'agent', 'agent_url': 'https://attestor.example/adcp'}, + {'type': 'origin', 'origin': 'https://credentials.example'}, + ], + title='Attestation Issuer', + ), +] diff --git a/src/adcp/types/generated_poc/core/attestation_subject.py b/src/adcp/types/generated_poc/core/attestation_subject.py index 2e49d2403..ff4620b51 100644 --- a/src/adcp/types/generated_poc/core/attestation_subject.py +++ b/src/adcp/types/generated_poc/core/attestation_subject.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/attestation_subject.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel +from pydantic import AnyUrl, ConfigDict, Field from . import brand_ref from . import ext as ext_1 @@ -68,31 +68,23 @@ class AttestationSubject1(AdCPBaseModel): ext: ext_1.ExtensionObject | None = None -class AttestationSubject( - RootModel[AttestationSubject1 | AttestationSubject2 | AttestationSubject3] -): - root: Annotated[ - AttestationSubject1 | AttestationSubject2 | AttestationSubject3, - Field( - description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', - discriminator='type', - examples=[ - { - 'type': 'brand', - 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, - }, - { - 'type': 'resource', - 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', - 'namespace': 'https://signals.meridian.example/adcp', - 'id': 'signal_urban_commuters', - }, - ], - title='Attestation Subject', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AttestationSubject = Annotated[ + AttestationSubject1 | AttestationSubject2 | AttestationSubject3, + Field( + description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', + discriminator='type', + examples=[ + { + 'type': 'brand', + 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, + }, + { + 'type': 'resource', + 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', + 'namespace': 'https://signals.meridian.example/adcp', + 'id': 'signal_urban_commuters', + }, + ], + title='Attestation Subject', + ), +] diff --git a/src/adcp/types/generated_poc/core/audience_activation_method.py b/src/adcp/types/generated_poc/core/audience_activation_method.py index 7c899cadd..f70fee9f7 100644 --- a/src/adcp/types/generated_poc/core/audience_activation_method.py +++ b/src/adcp/types/generated_poc/core/audience_activation_method.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/audience_activation_method.json -# timestamp: 2026-08-28T20:03:29+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel +from pydantic import AnyUrl, ConfigDict, Field from ..enums import cloud_storage_protocol from . import brand_ref @@ -151,62 +151,47 @@ class AudienceActivationMethod6(AdCPBaseModel): ] = None -class AudienceActivationMethod( - RootModel[ - AudienceActivationMethod1 - | AudienceActivationMethod2 - | AudienceActivationMethod3 - | AudienceActivationMethod4 - | AudienceActivationMethod5 - | AudienceActivationMethod6 - ] -): - root: Annotated[ - AudienceActivationMethod1 - | AudienceActivationMethod2 - | AudienceActivationMethod3 - | AudienceActivationMethod4 - | AudienceActivationMethod5 - | AudienceActivationMethod6, - Field( - description='One way buyer audience data can reach a seller for targeting. The pattern field discriminates the entry; each pattern carries only its own fields. Two patterns are AdCP-canonical (sync_audiences, tmp_identity_match); the rest describe integration paths that move data outside the protocol while remaining discoverable through it. Vendor identity is a BrandRef domain so new platforms declare a domain rather than waiting on an enum change. Experimental (x-status: experimental): the audience-activation surface — this schema, product.audience_activation, audience_targeting.supported_activation_methods, and the audience_activation_methods product filter — is new and not yet field-tested across parties. Sellers that implement it MUST list media_buy.audience_activation in experimental_features. Per docs/reference/experimental-status, it MAY change between 3.x releases with notice.', - examples=[ - {'pattern': 'sync_audiences'}, - { - 'pattern': 'tmp_identity_match', - 'buyer_agent': {'agent_url': 'https://buyer.example-agent.com'}, - }, - { - 'pattern': 'file_transfer', - 'transport': 's3', - 'directions': ['buyer_to_seller'], - 'vendor': {'domain': 'object-store.example'}, - }, - { - 'pattern': 'dataset_query', - 'vendor': {'domain': 'data-cloud.example'}, - 'consumer_identities': [ - { - 'cloud': 'aws', - 'region': 'us-east-1', - 'identity': 'SELLERORG.ADCP_INGEST', - }, - {'identity': 'serviceAccount:adcp-ingest@publisher.example'}, - ], - }, - {'pattern': 'clean_room', 'vendor': {'domain': 'collaboration-room.example'}}, - { - 'pattern': 'platform_distribution', - 'vendor': {'domain': 'activation-hub.example'}, - 'destination_ref': 'dest_12345', - 'bind_expiry_days': 14, - }, - ], - title='Audience Activation Method', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AudienceActivationMethod = Annotated[ + AudienceActivationMethod1 + | AudienceActivationMethod2 + | AudienceActivationMethod3 + | AudienceActivationMethod4 + | AudienceActivationMethod5 + | AudienceActivationMethod6, + Field( + description='One way buyer audience data can reach a seller for targeting. The pattern field discriminates the entry; each pattern carries only its own fields. Two patterns are AdCP-canonical (sync_audiences, tmp_identity_match); the rest describe integration paths that move data outside the protocol while remaining discoverable through it. Vendor identity is a BrandRef domain so new platforms declare a domain rather than waiting on an enum change. Experimental (x-status: experimental): the audience-activation surface — this schema, product.audience_activation, audience_targeting.supported_activation_methods, and the audience_activation_methods product filter — is new and not yet field-tested across parties. Sellers that implement it MUST list media_buy.audience_activation in experimental_features. Per docs/reference/experimental-status, it MAY change between 3.x releases with notice.', + examples=[ + {'pattern': 'sync_audiences'}, + { + 'pattern': 'tmp_identity_match', + 'buyer_agent': {'agent_url': 'https://buyer.example-agent.com'}, + }, + { + 'pattern': 'file_transfer', + 'transport': 's3', + 'directions': ['buyer_to_seller'], + 'vendor': {'domain': 'object-store.example'}, + }, + { + 'pattern': 'dataset_query', + 'vendor': {'domain': 'data-cloud.example'}, + 'consumer_identities': [ + { + 'cloud': 'aws', + 'region': 'us-east-1', + 'identity': 'SELLERORG.ADCP_INGEST', + }, + {'identity': 'serviceAccount:adcp-ingest@publisher.example'}, + ], + }, + {'pattern': 'clean_room', 'vendor': {'domain': 'collaboration-room.example'}}, + { + 'pattern': 'platform_distribution', + 'vendor': {'domain': 'activation-hub.example'}, + 'destination_ref': 'dest_12345', + 'bind_expiry_days': 14, + }, + ], + title='Audience Activation Method', + ), +] diff --git a/src/adcp/types/generated_poc/core/audience_characteristic.py b/src/adcp/types/generated_poc/core/audience_characteristic.py index fd4077e48..3458ade8f 100644 --- a/src/adcp/types/generated_poc/core/audience_characteristic.py +++ b/src/adcp/types/generated_poc/core/audience_characteristic.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/audience_characteristic.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -30,6 +30,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class Range(AdCPBaseModel): model_config = ConfigDict( extra='forbid', diff --git a/src/adcp/types/generated_poc/core/audience_evidence.py b/src/adcp/types/generated_poc/core/audience_evidence.py index 2719e219c..24cf569eb 100644 --- a/src/adcp/types/generated_poc/core/audience_evidence.py +++ b/src/adcp/types/generated_poc/core/audience_evidence.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/audience_evidence.json -# timestamp: 2026-09-05T19:08:34+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -260,6 +260,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class Subject21(Subject11): pass @@ -300,6 +302,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class Subject31(Subject11): pass @@ -364,6 +368,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class AttestationRef(AttestationReference): subject: Subject34 diff --git a/src/adcp/types/generated_poc/core/audience_selector.py b/src/adcp/types/generated_poc/core/audience_selector.py index 7f35e5c0b..0459da85c 100644 --- a/src/adcp/types/generated_poc/core/audience_selector.py +++ b/src/adcp/types/generated_poc/core/audience_selector.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/audience_selector.json -# timestamp: 2026-06-04T19:44:00+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from . import signal_id as signal_id_1 from . import signal_ref as signal_ref_1 @@ -128,18 +128,10 @@ class AudienceSelector3(AdCPBaseModel): ] = None -class AudienceSelector( - RootModel[AudienceSelector1 | AudienceSelector2 | AudienceSelector3 | AudienceSelector4] -): - root: Annotated[ - AudienceSelector1 | AudienceSelector2 | AudienceSelector3 | AudienceSelector4, - Field( - description="Selects an audience by signal reference or natural language description. Uses 'type' as the primary discriminator (signal vs description). Signal selectors additionally use 'value_type' to determine the targeting expression format (matching signal-targeting.json variants).", - title='Audience Selector', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AudienceSelector = Annotated[ + AudienceSelector1 | AudienceSelector2 | AudienceSelector3 | AudienceSelector4, + Field( + description="Selects an audience by signal reference or natural language description. Uses 'type' as the primary discriminator (signal vs description). Signal selectors additionally use 'value_type' to determine the targeting expression format (matching signal-targeting.json variants).", + title='Audience Selector', + ), +] diff --git a/src/adcp/types/generated_poc/core/audience_source.py b/src/adcp/types/generated_poc/core/audience_source.py index b622ae043..d4ac99053 100644 --- a/src/adcp/types/generated_poc/core/audience_source.py +++ b/src/adcp/types/generated_poc/core/audience_source.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/audience_source.json -# timestamp: 2026-08-28T20:03:29+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, RootModel +from pydantic import AwareDatetime, ConfigDict, Field from . import brand_ref @@ -55,29 +55,23 @@ class AudienceSource2(AdCPBaseModel): ] -class AudienceSource(RootModel[AudienceSource1 | AudienceSource2]): - root: Annotated[ - AudienceSource1 | AudienceSource2, - Field( - description="External source reference for an audience: a pointer to buyer data that the seller ingests over a declared activation rail, instead of inline member deltas. The data never transits AdCP — the protocol carries the reference; ingestion happens on the vendor's rails. kind discriminates the entry. A buyer MUST only send a source kind whose corresponding activation pattern (dataset_query for dataset, platform_distribution for platform_segment) the seller has declared via audience_activation; sellers reject undeclared kinds with UNSUPPORTED_FEATURE. References are never credentials: grant-based paths are in-protocol only when the vendor flow is grantee-identified (the grant names a principal; no secret is conveyed), and requests carrying credential material are rejected with CREDENTIAL_IN_ARGS. Experimental (x-status: experimental): part of the media_buy.audience_activation surface — sellers implementing external sources MUST list media_buy.audience_activation in experimental_features. Per docs/reference/experimental-status, this surface MAY change between 3.x releases with notice.", - examples=[ - { - 'kind': 'dataset', - 'vendor': {'domain': 'data-cloud.example'}, - 'locator': 'PINNACLE.AUDIENCES.HIGH_VALUE_V3', - 'access_expires_at': '2026-12-01T00:00:00Z', - }, - { - 'kind': 'platform_segment', - 'vendor': {'domain': 'activation-hub.example'}, - 'segment_ref': 'seg_88213', - }, - ], - title='Audience Source', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AudienceSource = Annotated[ + AudienceSource1 | AudienceSource2, + Field( + description="External source reference for an audience: a pointer to buyer data that the seller ingests over a declared activation rail, instead of inline member deltas. The data never transits AdCP — the protocol carries the reference; ingestion happens on the vendor's rails. kind discriminates the entry. A buyer MUST only send a source kind whose corresponding activation pattern (dataset_query for dataset, platform_distribution for platform_segment) the seller has declared via audience_activation; sellers reject undeclared kinds with UNSUPPORTED_FEATURE. References are never credentials: grant-based paths are in-protocol only when the vendor flow is grantee-identified (the grant names a principal; no secret is conveyed), and requests carrying credential material are rejected with CREDENTIAL_IN_ARGS. Experimental (x-status: experimental): part of the media_buy.audience_activation surface — sellers implementing external sources MUST list media_buy.audience_activation in experimental_features. Per docs/reference/experimental-status, this surface MAY change between 3.x releases with notice.", + examples=[ + { + 'kind': 'dataset', + 'vendor': {'domain': 'data-cloud.example'}, + 'locator': 'PINNACLE.AUDIENCES.HIGH_VALUE_V3', + 'access_expires_at': '2026-12-01T00:00:00Z', + }, + { + 'kind': 'platform_segment', + 'vendor': {'domain': 'activation-hub.example'}, + 'segment_ref': 'seg_88213', + }, + ], + title='Audience Source', + ), +] diff --git a/src/adcp/types/generated_poc/core/brand_response_authorization_result.py b/src/adcp/types/generated_poc/core/brand_response_authorization_result.py index c741ee914..33a5de9f6 100644 --- a/src/adcp/types/generated_poc/core/brand_response_authorization_result.py +++ b/src/adcp/types/generated_poc/core/brand_response_authorization_result.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/brand_response_authorization_result.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel +from pydantic import AnyUrl, ConfigDict, Field class Trust(StrEnum): @@ -71,18 +71,10 @@ class BrandResponseAuthorizationResult2(AdCPBaseModel): ] = None -class BrandResponseAuthorizationResult( - RootModel[BrandResponseAuthorizationResult1 | BrandResponseAuthorizationResult2] -): - root: Annotated[ - BrandResponseAuthorizationResult1 | BrandResponseAuthorizationResult2, - Field( - description='Verifier-local result of the additive 3.2 brand.json authorization cross-check for a cryptographically valid verify_brand_claim or verify_brand_claims signed response. This object is produced by the verifier and is not a field asserted by the responding brand agent.', - title='Brand Response Authorization Result', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +BrandResponseAuthorizationResult = Annotated[ + BrandResponseAuthorizationResult1 | BrandResponseAuthorizationResult2, + Field( + description='Verifier-local result of the additive 3.2 brand.json authorization cross-check for a cryptographically valid verify_brand_claim or verify_brand_claims signed response. This object is produced by the verifier and is not a field asserted by the responding brand agent.', + title='Brand Response Authorization Result', + ), +] diff --git a/src/adcp/types/generated_poc/core/budget_allocation.py b/src/adcp/types/generated_poc/core/budget_allocation.py index a688c4b2c..b38e648a2 100644 --- a/src/adcp/types/generated_poc/core/budget_allocation.py +++ b/src/adcp/types/generated_poc/core/budget_allocation.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/budget_allocation.json -# timestamp: 2026-10-01T07:57:26+00:00 +# timestamp: 2026-10-03T00:36:50+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..enums import event_type as event_type_1 from ..enums import reach_unit as reach_unit_1 @@ -326,13 +326,8 @@ class OptimizationGoal7(OptimizationGoal3, OptimizationGoal4): pass -class OptimizationGoal(RootModel[OptimizationGoal5 | OptimizationGoal6 | OptimizationGoal7]): - root: OptimizationGoal5 | OptimizationGoal6 | OptimizationGoal7 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +OptimizationGoal = OptimizationGoal5 | OptimizationGoal6 | OptimizationGoal7 + class BudgetAllocation2(AdCPBaseModel): model_config = ConfigDict( @@ -353,17 +348,11 @@ class BudgetAllocation2(AdCPBaseModel): ] -class BudgetAllocation(RootModel[BudgetAllocation1 | BudgetAllocation2]): - root: Annotated[ - BudgetAllocation1 | BudgetAllocation2, - Field( - description='How a media buy\'s total budget is allocated across its packages. Omission on a request means fixed allocation (legacy-compatible); buyer agents SHOULD send this block explicitly ({mode: "fixed"} or seller_optimized) rather than rely on omission, and SHOULD ask the principal rather than guess when the instruction does not determine whether the budget is one shared pool or split per package. Fixed allocation preserves independent package budgets. Seller-optimized allocation delegates continuous cross-package allocation to the seller within the media-buy total, package caps, minimum-spend targets, flight windows, and pacing controls. The core seller-optimized contract is gated by media_buy.features.seller_optimized_budget; package caps, minimum-spend targets, and package pacing are separately gated by seller_optimized_package_budgets, seller_optimized_min_spend_targets, and seller_optimized_package_pacing; an undeclared package control is rejected with UNSUPPORTED_FEATURE before any over-subscription validation. Core seller-optimized pacing guarantees only omitted or `even` media-buy pacing; sellers MAY reject `asap` or `front_loaded` with UNSUPPORTED_FEATURE and MUST NOT coerce them to `even`. When a seller-optimized media-buy BiddingPolicy carries cost_per or roas, that control binds to this allocation block\'s primary optimization goal rather than independently to each package goal.', - discriminator='mode', - title='Budget Allocation', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +BudgetAllocation = Annotated[ + BudgetAllocation1 | BudgetAllocation2, + Field( + description='How a media buy\'s total budget is allocated across its packages. Omission on a request means fixed allocation (legacy-compatible); buyer agents SHOULD send this block explicitly ({mode: "fixed"} or seller_optimized) rather than rely on omission, and SHOULD ask the principal rather than guess when the instruction does not determine whether the budget is one shared pool or split per package. Fixed allocation preserves independent package budgets. Seller-optimized allocation delegates continuous cross-package allocation to the seller within the media-buy total, package caps, minimum-spend targets, flight windows, and pacing controls. The core seller-optimized contract is gated by media_buy.features.seller_optimized_budget; package caps, minimum-spend targets, and package pacing are separately gated by seller_optimized_package_budgets, seller_optimized_min_spend_targets, and seller_optimized_package_pacing; an undeclared package control is rejected with UNSUPPORTED_FEATURE before any over-subscription validation. Core seller-optimized pacing guarantees only omitted or `even` media-buy pacing; sellers MAY reject `asap` or `front_loaded` with UNSUPPORTED_FEATURE and MUST NOT coerce them to `even`. When a seller-optimized media-buy BiddingPolicy carries cost_per or roas, that control binds to this allocation block\'s primary optimization goal rather than independently to each package goal.', + discriminator='mode', + title='Budget Allocation', + ), +] diff --git a/src/adcp/types/generated_poc/core/canonical_account_ref.py b/src/adcp/types/generated_poc/core/canonical_account_ref.py index c9fdb0485..706fd00a4 100644 --- a/src/adcp/types/generated_poc/core/canonical_account_ref.py +++ b/src/adcp/types/generated_poc/core/canonical_account_ref.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/canonical_account_ref.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated +from typing import Annotated from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from . import brand_key from . import operator_unit as operator_unit_1 @@ -51,26 +51,20 @@ class CanonicalAccountReference2(AdCPBaseModel): sandbox: bool | None = False -class CanonicalAccountReference(RootModel[CanonicalAccountReference1 | CanonicalAccountReference2]): - root: Annotated[ - CanonicalAccountReference1 | CanonicalAccountReference2, - Field( - description='Compact advertiser-account identity for canonical 3.2 tools. Uses either the seller/storefront account_id returned by list_accounts or the buyer-declared natural key without BrandRef inline overrides. The natural key is brand + operator + optional operator_unit.id + optional fixed currency + optional buyer-selected account timezone + sandbox. brand.countries qualifies the commercial advertiser identity without targeting delivery; operator_unit identifies the operator-owned buying context. operator_unit.name is display metadata and is excluded from identity.', - examples=[ - {'account_id': 'acc_nova_nl'}, - { - 'brand': {'domain': 'nova-athletics.example', 'countries': ['NL']}, - 'operator': 'nova-athletics.example', - 'operator_unit': {'id': '234284238', 'name': 'Nova EMEA'}, - 'currency': 'EUR', - 'timezone': 'Europe/Amsterdam', - }, - ], - title='Canonical Account Reference', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +CanonicalAccountReference = Annotated[ + CanonicalAccountReference1 | CanonicalAccountReference2, + Field( + description='Compact advertiser-account identity for canonical 3.2 tools. Uses either the seller/storefront account_id returned by list_accounts or the buyer-declared natural key without BrandRef inline overrides. The natural key is brand + operator + optional operator_unit.id + optional fixed currency + optional buyer-selected account timezone + sandbox. brand.countries qualifies the commercial advertiser identity without targeting delivery; operator_unit identifies the operator-owned buying context. operator_unit.name is display metadata and is excluded from identity.', + examples=[ + {'account_id': 'acc_nova_nl'}, + { + 'brand': {'domain': 'nova-athletics.example', 'countries': ['NL']}, + 'operator': 'nova-athletics.example', + 'operator_unit': {'id': '234284238', 'name': 'Nova EMEA'}, + 'currency': 'EUR', + 'timezone': 'Europe/Amsterdam', + }, + ], + title='Canonical Account Reference', + ), +] diff --git a/src/adcp/types/generated_poc/core/canonical_budget_allocation.py b/src/adcp/types/generated_poc/core/canonical_budget_allocation.py index 4d70789e1..1cd3fc12f 100644 --- a/src/adcp/types/generated_poc/core/canonical_budget_allocation.py +++ b/src/adcp/types/generated_poc/core/canonical_budget_allocation.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/canonical_budget_allocation.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from . import canonical_optimization_goal @@ -29,17 +29,11 @@ class CanonicalBudgetAllocation2(AdCPBaseModel): ] -class CanonicalBudgetAllocation(RootModel[CanonicalBudgetAllocation1 | CanonicalBudgetAllocation2]): - root: Annotated[ - CanonicalBudgetAllocation1 | CanonicalBudgetAllocation2, - Field( - description='Canonical fixed or seller-optimized allocation using only canonical optimization goals.', - discriminator='mode', - title='Canonical Budget Allocation', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +CanonicalBudgetAllocation = Annotated[ + CanonicalBudgetAllocation1 | CanonicalBudgetAllocation2, + Field( + description='Canonical fixed or seller-optimized allocation using only canonical optimization goals.', + discriminator='mode', + title='Canonical Budget Allocation', + ), +] diff --git a/src/adcp/types/generated_poc/core/canonical_media_buy_action.py b/src/adcp/types/generated_poc/core/canonical_media_buy_action.py index a1afb1cf7..75b00f1c0 100644 --- a/src/adcp/types/generated_poc/core/canonical_media_buy_action.py +++ b/src/adcp/types/generated_poc/core/canonical_media_buy_action.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/canonical_media_buy_action.json -# timestamp: 2026-09-14T14:16:02+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal -from pydantic import Field, RootModel +from pydantic import Field from .canonical_media_buy_action_fields import CanonicalMediaBuyActionFields @@ -74,19 +74,11 @@ class CanonicalMediaBuyAction3(CanonicalMediaBuyActionFields): action: Action4 | None = None -class CanonicalMediaBuyAction( - RootModel[CanonicalMediaBuyAction1 | CanonicalMediaBuyAction2 | CanonicalMediaBuyAction3] -): - root: Annotated[ - CanonicalMediaBuyAction1 | CanonicalMediaBuyAction2 | CanonicalMediaBuyAction3, - Field( - description='A currently available MediaBuy action with the compact-lifecycle task an SDK calls to exercise it. Deprecated coarse action values are absent.', - discriminator='task', - title='Canonical Media Buy Action', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +CanonicalMediaBuyAction = Annotated[ + CanonicalMediaBuyAction1 | CanonicalMediaBuyAction2 | CanonicalMediaBuyAction3, + Field( + description='A currently available MediaBuy action with the compact-lifecycle task an SDK calls to exercise it. Deprecated coarse action values are absent.', + discriminator='task', + title='Canonical Media Buy Action', + ), +] diff --git a/src/adcp/types/generated_poc/core/canonical_optimization_goal.py b/src/adcp/types/generated_poc/core/canonical_optimization_goal.py index 8b487096a..f0228d1ba 100644 --- a/src/adcp/types/generated_poc/core/canonical_optimization_goal.py +++ b/src/adcp/types/generated_poc/core/canonical_optimization_goal.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/canonical_optimization_goal.json -# timestamp: 2026-09-27T20:53:10+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..enums import event_type as event_type_1 from ..enums import reach_unit as reach_unit_1 @@ -106,19 +106,11 @@ class CanonicalOptimizationGoal3(AdCPBaseModel): priority: Annotated[int | None, Field(ge=1)] = None -class CanonicalOptimizationGoal( - RootModel[CanonicalOptimizationGoal1 | CanonicalOptimizationGoal2 | CanonicalOptimizationGoal3] -): - root: Annotated[ - CanonicalOptimizationGoal1 | CanonicalOptimizationGoal2 | CanonicalOptimizationGoal3, - Field( - description='Canonical 3.2 optimization objective. Monetary execution policy belongs in BiddingPolicy; legacy monetary targets and unbound attention metrics are excluded.', - discriminator='kind', - title='Canonical Optimization Goal', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +CanonicalOptimizationGoal = Annotated[ + CanonicalOptimizationGoal1 | CanonicalOptimizationGoal2 | CanonicalOptimizationGoal3, + Field( + description='Canonical 3.2 optimization objective. Monetary execution policy belongs in BiddingPolicy; legacy monetary targets and unbound attention metrics are excluded.', + discriminator='kind', + title='Canonical Optimization Goal', + ), +] diff --git a/src/adcp/types/generated_poc/core/canonical_placement.py b/src/adcp/types/generated_poc/core/canonical_placement.py index e5f7e187f..629476f10 100644 --- a/src/adcp/types/generated_poc/core/canonical_placement.py +++ b/src/adcp/types/generated_poc/core/canonical_placement.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/canonical_placement.json -# timestamp: 2026-09-01T19:37:00+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..enums import ( audio_distribution_type, @@ -170,16 +170,10 @@ class CanonicalProductPlacement2(AdCPBaseModel): dooh_placement_attributes: CanonicalDoohPlacementAttributes | None = None -class CanonicalProductPlacement(RootModel[CanonicalProductPlacement1 | CanonicalProductPlacement2]): - root: Annotated[ - CanonicalProductPlacement1 | CanonicalProductPlacement2, - Field( - description='Compact product placement with canonical format narrowing. Publisher references carry their publisher authority. New seller-inline placements SHOULD carry seller_agent as their defining authority; legacy seller-inline placements without it remain valid in product context.', - title='Canonical Product Placement', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +CanonicalProductPlacement = Annotated[ + CanonicalProductPlacement1 | CanonicalProductPlacement2, + Field( + description='Compact product placement with canonical format narrowing. Publisher references carry their publisher authority. New seller-inline placements SHOULD carry seller_agent as their defining authority; legacy seller-inline placements without it remain valid in product context.', + title='Canonical Product Placement', + ), +] diff --git a/src/adcp/types/generated_poc/core/canonical_product.py b/src/adcp/types/generated_poc/core/canonical_product.py index 388fe0681..098cbbd16 100644 --- a/src/adcp/types/generated_poc/core/canonical_product.py +++ b/src/adcp/types/generated_poc/core/canonical_product.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/canonical_product.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field, RootModel +from pydantic import AwareDatetime, ConfigDict, Field from ..enums import audio_distribution_type, catalog_type from ..enums import channels as channels_1 @@ -159,13 +159,8 @@ class PublisherProperty7(PublisherProperty3, PublisherProperty4): pass -class PublisherProperty(RootModel[PublisherProperty5 | PublisherProperty6 | PublisherProperty7]): - root: PublisherProperty5 | PublisherProperty6 | PublisherProperty7 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +PublisherProperty = PublisherProperty5 | PublisherProperty6 | PublisherProperty7 + class CanonicalProduct(AdCPBaseModel): model_config = ConfigDict( diff --git a/src/adcp/types/generated_poc/core/canonical_reporting_commitment.py b/src/adcp/types/generated_poc/core/canonical_reporting_commitment.py index dce1b62df..e7d121d35 100644 --- a/src/adcp/types/generated_poc/core/canonical_reporting_commitment.py +++ b/src/adcp/types/generated_poc/core/canonical_reporting_commitment.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/canonical_reporting_commitment.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, RootModel +from pydantic import AwareDatetime, ConfigDict, Field from ..enums import available_metric from . import brand_key, canonical_metric_qualifier, vendor_metric_id @@ -34,19 +34,11 @@ class CanonicalReportingCommitment2(AdCPBaseModel): effective_at: AwareDatetime | None = None -class CanonicalReportingCommitment( - RootModel[CanonicalReportingCommitment1 | CanonicalReportingCommitment2] -): - root: Annotated[ - CanonicalReportingCommitment1 | CanonicalReportingCommitment2, - Field( - description='One metric the seller commits to report for a commercial product term. Vendor identity uses BrandKey so the compact lifecycle does not inline brand or provenance documents.', - discriminator='scope', - title='Canonical Reporting Commitment', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +CanonicalReportingCommitment = Annotated[ + CanonicalReportingCommitment1 | CanonicalReportingCommitment2, + Field( + description='One metric the seller commits to report for a commercial product term. Vendor identity uses BrandKey so the compact lifecycle does not inline brand or provenance documents.', + discriminator='scope', + title='Canonical Reporting Commitment', + ), +] diff --git a/src/adcp/types/generated_poc/core/collection_selection.py b/src/adcp/types/generated_poc/core/collection_selection.py index 44c770899..334f3ed50 100644 --- a/src/adcp/types/generated_poc/core/collection_selection.py +++ b/src/adcp/types/generated_poc/core/collection_selection.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/collection_selection.json -# timestamp: 2026-09-01T19:37:00+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from . import collection_selector from . import ext as ext_1 @@ -36,17 +36,11 @@ class CollectionSelection2(AdCPBaseModel): ext: ext_1.ExtensionObject | None = None -class CollectionSelection(RootModel[CollectionSelection1 | CollectionSelection2]): - root: Annotated[ - CollectionSelection1 | CollectionSelection2, - Field( - description="Complete purchased collection set within a product. Mode selected names the complete required set (partial selection requires product collection_targeting_allowed: true; exactly restating the full bundle is an inherent match); mode default accepts the product's full bundle. Package readback MUST echo the committed selection here as concrete selectors, materializing any collection_list composition; a purchase made with mode default therefore reads back as mode selected naming the product's complete bundle. Rejection of a schema-valid selector that references an unknown collection_id or publisher_domain uses REFERENCE_NOT_FOUND per the error-code registry (INVALID_REQUEST applies only to schema-invalid shapes such as a selected-mode item omitting collection_ids). Sellers reject unknown, duplicate, or partially applicable selectors rather than silently changing the selection.", - discriminator='mode', - title='Collection Selection', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +CollectionSelection = Annotated[ + CollectionSelection1 | CollectionSelection2, + Field( + description="Complete purchased collection set within a product. Mode selected names the complete required set (partial selection requires product collection_targeting_allowed: true; exactly restating the full bundle is an inherent match); mode default accepts the product's full bundle. Package readback MUST echo the committed selection here as concrete selectors, materializing any collection_list composition; a purchase made with mode default therefore reads back as mode selected naming the product's complete bundle. Rejection of a schema-valid selector that references an unknown collection_id or publisher_domain uses REFERENCE_NOT_FOUND per the error-code registry (INVALID_REQUEST applies only to schema-invalid shapes such as a selected-mode item omitting collection_ids). Sellers reject unknown, duplicate, or partially applicable selectors rather than silently changing the selection.", + discriminator='mode', + title='Collection Selection', + ), +] diff --git a/src/adcp/types/generated_poc/core/committed_metric.py b/src/adcp/types/generated_poc/core/committed_metric.py index e2bc272c1..edb267c8c 100644 --- a/src/adcp/types/generated_poc/core/committed_metric.py +++ b/src/adcp/types/generated_poc/core/committed_metric.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/committed_metric.json -# timestamp: 2026-09-05T11:09:57+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, RootModel +from pydantic import AwareDatetime, ConfigDict, Field from ..enums import attribution_methodology as attribution_methodology_1 from ..enums import available_metric @@ -148,17 +148,11 @@ class CommittedMetric2(AdCPBaseModel): ] -class CommittedMetric(RootModel[CommittedMetric1 | CommittedMetric2]): - root: Annotated[ - CommittedMetric1 | CommittedMetric2, - Field( - description="One metric in a package's binding reporting contract. Each entry uses `scope` as the discriminator and identifies a standard or vendor-defined metric that the seller has committed to populate in delivery reports.", - discriminator='scope', - title='Committed Metric', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +CommittedMetric = Annotated[ + CommittedMetric1 | CommittedMetric2, + Field( + description="One metric in a package's binding reporting contract. Each entry uses `scope` as the discriminator and identifies a standard or vendor-defined metric that the seller has committed to populate in delivery reports.", + discriminator='scope', + title='Committed Metric', + ), +] diff --git a/src/adcp/types/generated_poc/core/creative_filters.py b/src/adcp/types/generated_poc/core/creative_filters.py index 0b09c25b6..50c57610d 100644 --- a/src/adcp/types/generated_poc/core/creative_filters.py +++ b/src/adcp/types/generated_poc/core/creative_filters.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/creative_filters.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -20,7 +20,7 @@ class CreativeFilters(AdCPBaseModel): extra='allow', ) accounts: Annotated[ - list[account_ref.AccountReference1 | account_ref.AccountReference2] | None, + list[account_ref.AccountReference] | None, Field( description='Filter creatives by owning accounts. Useful for agencies managing multiple client accounts.', min_length=1, diff --git a/src/adcp/types/generated_poc/core/creative_item.py b/src/adcp/types/generated_poc/core/creative_item.py index 9734dfe98..8d7210e69 100644 --- a/src/adcp/types/generated_poc/core/creative_item.py +++ b/src/adcp/types/generated_poc/core/creative_item.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/creative_item.json -# timestamp: 2026-06-01T00:32:59+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel +from pydantic import AnyUrl, ConfigDict, Field class CreativeItem1(AdCPBaseModel): @@ -55,17 +55,11 @@ class CreativeItem2(AdCPBaseModel): ] -class CreativeItem(RootModel[CreativeItem1 | CreativeItem2]): - root: Annotated[ - CreativeItem1 | CreativeItem2, - Field( - description='Item within a multi-asset creative format. Used for carousel products, native ad components, and other formats composed of multiple distinct elements.', - discriminator='asset_kind', - title='Creative Item', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +CreativeItem = Annotated[ + CreativeItem1 | CreativeItem2, + Field( + description='Item within a multi-asset creative format. Used for carousel products, native ad components, and other formats composed of multiple distinct elements.', + discriminator='asset_kind', + title='Creative Item', + ), +] diff --git a/src/adcp/types/generated_poc/core/creative_localization_readback.py b/src/adcp/types/generated_poc/core/creative_localization_readback.py index 2c530e89f..b8c16b238 100644 --- a/src/adcp/types/generated_poc/core/creative_localization_readback.py +++ b/src/adcp/types/generated_poc/core/creative_localization_readback.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/creative_localization_readback.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-03T00:36:50+00:00 from __future__ import annotations @@ -51,6 +51,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class Variants1(AdCPBaseModel): model_config = ConfigDict( extra='allow', @@ -85,13 +87,11 @@ class Variants2(AdCPBaseModel): assets: ResolvedAssets -class Variants(RootModel[Variants1 | Variants2]): - root: Annotated[Variants1 | Variants2, Field(discriminator='role')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +Variants = Annotated[ + Variants1 | Variants2, + Field(discriminator='role'), +] + class CreativeLocalizationReadback(AdCPBaseModel): model_config = ConfigDict( diff --git a/src/adcp/types/generated_poc/core/data_provider_signal_selector.py b/src/adcp/types/generated_poc/core/data_provider_signal_selector.py index 1c196d193..8de6cfde2 100644 --- a/src/adcp/types/generated_poc/core/data_provider_signal_selector.py +++ b/src/adcp/types/generated_poc/core/data_provider_signal_selector.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/data_provider_signal_selector.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field class DataProviderSignalSelector1(AdCPBaseModel): @@ -87,21 +87,11 @@ class DataProviderSignalSelector3(AdCPBaseModel): ] -class DataProviderSignalSelector( - RootModel[ - DataProviderSignalSelector1 | DataProviderSignalSelector2 | DataProviderSignalSelector3 - ] -): - root: Annotated[ - DataProviderSignalSelector1 | DataProviderSignalSelector2 | DataProviderSignalSelector3, - Field( - description="Selects signals from a data provider's adagents.json signals[]. Used for product definitions and agent authorization. Supports three selection patterns: all signals, specific IDs, or by tags.", - discriminator='selection_type', - title='Data Provider Signal Selector', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +DataProviderSignalSelector = Annotated[ + DataProviderSignalSelector1 | DataProviderSignalSelector2 | DataProviderSignalSelector3, + Field( + description="Selects signals from a data provider's adagents.json signals[]. Used for product definitions and agent authorization. Supports three selection patterns: all signals, specific IDs, or by tags.", + discriminator='selection_type', + title='Data Provider Signal Selector', + ), +] diff --git a/src/adcp/types/generated_poc/core/delivery_metric_aggregate.py b/src/adcp/types/generated_poc/core/delivery_metric_aggregate.py index 47b61c03c..a75cfbfca 100644 --- a/src/adcp/types/generated_poc/core/delivery_metric_aggregate.py +++ b/src/adcp/types/generated_poc/core/delivery_metric_aggregate.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/delivery_metric_aggregate.json -# timestamp: 2026-09-05T19:08:34+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..enums import attribution_methodology as attribution_methodology_1 from ..enums import available_metric @@ -198,17 +198,11 @@ class DeliveryMetricAggregate2(AdCPBaseModel): ] = None -class DeliveryMetricAggregate(RootModel[DeliveryMetricAggregate1 | DeliveryMetricAggregate2]): - root: Annotated[ - DeliveryMetricAggregate1 | DeliveryMetricAggregate2, - Field( - description='One delivery metric value partitioned by metric scope and qualifier. Its standard branch is used at package grain by `by_package[].metric_values`; both branches are retained for the deprecated cross-buy `aggregated_totals.metric_aggregates` compatibility field. Row-symmetric with `package.committed_metrics` and delivery `missing_metrics` so buyers can reconcile by `(scope, metric_id, qualifier)`.', - discriminator='scope', - title='Delivery Metric Aggregate', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +DeliveryMetricAggregate = Annotated[ + DeliveryMetricAggregate1 | DeliveryMetricAggregate2, + Field( + description='One delivery metric value partitioned by metric scope and qualifier. Its standard branch is used at package grain by `by_package[].metric_values`; both branches are retained for the deprecated cross-buy `aggregated_totals.metric_aggregates` compatibility field. Row-symmetric with `package.committed_metrics` and delivery `missing_metrics` so buyers can reconcile by `(scope, metric_id, qualifier)`.', + discriminator='scope', + title='Delivery Metric Aggregate', + ), +] diff --git a/src/adcp/types/generated_poc/core/deployment.py b/src/adcp/types/generated_poc/core/deployment.py index 7354e3575..dddd8fafa 100644 --- a/src/adcp/types/generated_poc/core/deployment.py +++ b/src/adcp/types/generated_poc/core/deployment.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/deployment.json -# timestamp: 2026-06-01T00:32:59+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field from . import activation_key as activation_key_1 @@ -76,17 +76,11 @@ class Deployment2(AdCPBaseModel): ] = None -class Deployment(RootModel[Deployment1 | Deployment2]): - root: Annotated[ - Deployment1 | Deployment2, - Field( - description='A signal deployment to a specific deployment target with activation status and key', - discriminator='type', - title='Deployment', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +Deployment = Annotated[ + Deployment1 | Deployment2, + Field( + description='A signal deployment to a specific deployment target with activation status and key', + discriminator='type', + title='Deployment', + ), +] diff --git a/src/adcp/types/generated_poc/core/destination.py b/src/adcp/types/generated_poc/core/destination.py index 225d0d9c3..cbb8703fe 100644 --- a/src/adcp/types/generated_poc/core/destination.py +++ b/src/adcp/types/generated_poc/core/destination.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/destination.json -# timestamp: 2026-06-01T00:32:59+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel +from pydantic import AnyUrl, ConfigDict, Field class Destination1(AdCPBaseModel): @@ -43,17 +43,11 @@ class Destination2(AdCPBaseModel): ] = None -class Destination(RootModel[Destination1 | Destination2]): - root: Annotated[ - Destination1 | Destination2, - Field( - description='A deployment target where signals can be activated (DSP, sales agent, etc.)', - discriminator='type', - title='Destination', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +Destination = Annotated[ + Destination1 | Destination2, + Field( + description='A deployment target where signals can be activated (DSP, sales agent, etc.)', + discriminator='type', + title='Destination', + ), +] diff --git a/src/adcp/types/generated_poc/core/evaluator_spec.py b/src/adcp/types/generated_poc/core/evaluator_spec.py index 24e1bb3d3..4ae8c1bac 100644 --- a/src/adcp/types/generated_poc/core/evaluator_spec.py +++ b/src/adcp/types/generated_poc/core/evaluator_spec.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/evaluator_spec.json -# timestamp: 2026-06-07T22:46:16+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated +from typing import Annotated from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel +from pydantic import AnyUrl, ConfigDict, Field from ..content_standards import artifact from . import ext as ext_1 @@ -211,16 +211,10 @@ class EvaluatorSpec1(AdCPBaseModel): ] -class EvaluatorSpec(RootModel[EvaluatorSpec1 | EvaluatorSpec2 | EvaluatorSpec3]): - root: Annotated[ - EvaluatorSpec1 | EvaluatorSpec2 | EvaluatorSpec3, - Field( - description="Advisory buyer-attached pointer (#5280) declaring HOW produced build_creative variants should be evaluated and ranked. The rank-side of the get_creative_features feature oracle: the chosen form is the SOURCE of feature evaluation that yields creative-feature values for each leaf, `feature_requirement` is an optional hard GATE over those values, and `rank_by` is the RANK ordering over the survivors. Informs the agent's best_of_n recommended/rank and, when the agent advertises creative.supports_evaluator, populates a per-leaf `eval` block (creative-feature-result[]) on the response.\n\nGate-then-rank pipeline (per produced leaf in a best_of_n run): the producing agent (1) evaluates the leaf via the chosen form, obtaining creative-feature values; (2) when `feature_requirement` is present, applies those predicates and DROPS any leaf that fails (internal best_of_n pruning) — survivors only; (3) orders survivors by `rank_by`. The gate operates BEFORE return: it prunes which leaves the agent recommends/returns from its own best_of_n exploration, never an AdCP-layer block of an already-produced billable leaf — what is produced and billed is governed by max_variants/max_creatives/max_spend, not by the evaluator. With no `feature_requirement`, evaluation stays advisory: every produced leaf is returned and billed and ranking is a soft preference, so `rank_by` failures rank lower but are never dropped.\n\nWhen the chosen form is `feature_agent` (or `agent_url`), the seller calls an external get_creative_features-capable agent, so the pointer is subject to the seller's `creative_policy.accepted_verifiers[]` allowlist exactly as #5280 established for provenance verify_agent: the buyer REPRESENTS which on-list agent it used, the seller (verifier-of-record) CALLS it. An off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED` (mirrors PROVENANCE_VERIFIER_NOT_ACCEPTED) before any outbound call; an on-list agent that is unreachable or unknown degrades to seller-default ranking with an advisory errors[] note (non-terminal), NOT a failure.\n\nEvaluator credentials and caller-supplied trust material are never carried in this object, `context`, `ext`, or the creative payload. The producing creative agent authenticates its outbound call to the evaluator on the transport (RFC 9421 request signing/JWKS, mTLS, or a pre-provisioned Bearer/API-key credential). Credential- or trust-material payload keys (for example `api_key`, `client_secret`, `authorization`, `jwk`, `jwks`, or `jwks_uri`) are non-conforming and should be rejected with `CREDENTIAL_IN_ARGS`.\n\nProvide exactly one of three forms; an optional `feature_requirement` gate, an optional `rank_by` ordering, an optional `feature_agent` allowlisted pointer, and a shared optional eval_budget apply to whichever form is chosen. Feature discovery uses the existing governance.creative_features catalog on get_adcp_capabilities; evaluator outputs reuse creative-feature-result[] and do not mint a parallel verdict/result surface. `evaluator_id` is not discovered from governance.creative_features: it is a pre-provisioned, account-arranged preset whose emitted feature_ids still come from that catalog.\n\nExperimental (x-status: experimental): this whole evaluator surface — the `evaluator` input, the per-leaf `eval` response block, and `creative.supports_evaluator` — is new and not yet field-tested across parties. Sellers that implement it MUST list `creative.evaluator` in `experimental_features`. Reserved 3.x follow-ons that may reshape these fields: a separate `supports_evaluator_gate` capability and a hard MUST-enforce-gate semantic. Per docs/reference/experimental-status, it MAY change between 3.x releases with notice.", - title='Evaluator Spec', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +EvaluatorSpec = Annotated[ + EvaluatorSpec1 | EvaluatorSpec2 | EvaluatorSpec3, + Field( + description="Advisory buyer-attached pointer (#5280) declaring HOW produced build_creative variants should be evaluated and ranked. The rank-side of the get_creative_features feature oracle: the chosen form is the SOURCE of feature evaluation that yields creative-feature values for each leaf, `feature_requirement` is an optional hard GATE over those values, and `rank_by` is the RANK ordering over the survivors. Informs the agent's best_of_n recommended/rank and, when the agent advertises creative.supports_evaluator, populates a per-leaf `eval` block (creative-feature-result[]) on the response.\n\nGate-then-rank pipeline (per produced leaf in a best_of_n run): the producing agent (1) evaluates the leaf via the chosen form, obtaining creative-feature values; (2) when `feature_requirement` is present, applies those predicates and DROPS any leaf that fails (internal best_of_n pruning) — survivors only; (3) orders survivors by `rank_by`. The gate operates BEFORE return: it prunes which leaves the agent recommends/returns from its own best_of_n exploration, never an AdCP-layer block of an already-produced billable leaf — what is produced and billed is governed by max_variants/max_creatives/max_spend, not by the evaluator. With no `feature_requirement`, evaluation stays advisory: every produced leaf is returned and billed and ranking is a soft preference, so `rank_by` failures rank lower but are never dropped.\n\nWhen the chosen form is `feature_agent` (or `agent_url`), the seller calls an external get_creative_features-capable agent, so the pointer is subject to the seller's `creative_policy.accepted_verifiers[]` allowlist exactly as #5280 established for provenance verify_agent: the buyer REPRESENTS which on-list agent it used, the seller (verifier-of-record) CALLS it. An off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED` (mirrors PROVENANCE_VERIFIER_NOT_ACCEPTED) before any outbound call; an on-list agent that is unreachable or unknown degrades to seller-default ranking with an advisory errors[] note (non-terminal), NOT a failure.\n\nEvaluator credentials and caller-supplied trust material are never carried in this object, `context`, `ext`, or the creative payload. The producing creative agent authenticates its outbound call to the evaluator on the transport (RFC 9421 request signing/JWKS, mTLS, or a pre-provisioned Bearer/API-key credential). Credential- or trust-material payload keys (for example `api_key`, `client_secret`, `authorization`, `jwk`, `jwks`, or `jwks_uri`) are non-conforming and should be rejected with `CREDENTIAL_IN_ARGS`.\n\nProvide exactly one of three forms; an optional `feature_requirement` gate, an optional `rank_by` ordering, an optional `feature_agent` allowlisted pointer, and a shared optional eval_budget apply to whichever form is chosen. Feature discovery uses the existing governance.creative_features catalog on get_adcp_capabilities; evaluator outputs reuse creative-feature-result[] and do not mint a parallel verdict/result surface. `evaluator_id` is not discovered from governance.creative_features: it is a pre-provisioned, account-arranged preset whose emitted feature_ids still come from that catalog.\n\nExperimental (x-status: experimental): this whole evaluator surface — the `evaluator` input, the per-leaf `eval` response block, and `creative.supports_evaluator` — is new and not yet field-tested across parties. Sellers that implement it MUST list `creative.evaluator` in `experimental_features`. Reserved 3.x follow-ons that may reshape these fields: a separate `supports_evaluator_gate` capability and a hard MUST-enforce-gate semantic. Per docs/reference/experimental-status, it MAY change between 3.x releases with notice.", + title='Evaluator Spec', + ), +] diff --git a/src/adcp/types/generated_poc/core/format.py b/src/adcp/types/generated_poc/core/format.py index faede6679..173f04f10 100644 --- a/src/adcp/types/generated_poc/core/format.py +++ b/src/adcp/types/generated_poc/core/format.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/format.json -# timestamp: 2026-09-13T18:52:32+00:00 +# timestamp: 2026-10-03T00:36:50+00:00 from __future__ import annotations @@ -506,60 +506,31 @@ class Assets45(BaseGroupAsset): requirements: catalog_requirements.CatalogRequirements | None = None -class Assets30( - RootModel[ - Assets31 - | Assets32 - | Assets33 - | Assets34 - | Assets35 - | Assets36 - | Assets37 - | Assets38 - | Assets39 - | Assets40 - | Assets41 - | Assets42 - | Assets43 - | Assets44 - | Assets45 - | Assets46 - | Assets47 - | Assets48 - | Assets49 - | Assets50 - | Assets51 - ] -): - root: Annotated[ - Assets31 - | Assets32 - | Assets33 - | Assets34 - | Assets35 - | Assets36 - | Assets37 - | Assets38 - | Assets39 - | Assets40 - | Assets41 - | Assets42 - | Assets43 - | Assets44 - | Assets45 - | Assets46 - | Assets47 - | Assets48 - | Assets49 - | Assets50 - | Assets51, - Field(discriminator='asset_type'), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +Assets30 = Annotated[ + Assets31 + | Assets32 + | Assets33 + | Assets34 + | Assets35 + | Assets36 + | Assets37 + | Assets38 + | Assets39 + | Assets40 + | Assets41 + | Assets42 + | Assets43 + | Assets44 + | Assets45 + | Assets46 + | Assets47 + | Assets48 + | Assets49 + | Assets50 + | Assets51, + Field(discriminator='asset_type'), +] + class Assets29(AdCPBaseModel): item_type: Annotated[ @@ -2278,6 +2249,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class Format(AdCPBaseModel): model_config = ConfigDict( extra='allow', diff --git a/src/adcp/types/generated_poc/core/format_option_ref.py b/src/adcp/types/generated_poc/core/format_option_ref.py index 28c1a1506..1a2d5e5bb 100644 --- a/src/adcp/types/generated_poc/core/format_option_ref.py +++ b/src/adcp/types/generated_poc/core/format_option_ref.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/format_option_ref.json -# timestamp: 2026-06-01T00:32:59+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from typing import Annotated, Any, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field class FormatOptionReference1(AdCPBaseModel): @@ -54,17 +54,11 @@ class FormatOptionReference2(AdCPBaseModel): publisher_domain: Any | None = None -class FormatOptionReference(RootModel[FormatOptionReference1 | FormatOptionReference2]): - root: Annotated[ - FormatOptionReference1 | FormatOptionReference2, - Field( - description='Discriminated reference to a product format option. The global canonical shape is still named by `format_kind`; this reference selects one concrete product `format_options[]` entry. `scope: "publisher"` identifies a publisher-declared catalog option by `{ publisher_domain, format_option_id }`. `scope: "product"` identifies a product-local option by `format_option_id`; the enclosing package/product context supplies the namespace.', - discriminator='scope', - title='Format Option Reference', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +FormatOptionReference = Annotated[ + FormatOptionReference1 | FormatOptionReference2, + Field( + description='Discriminated reference to a product format option. The global canonical shape is still named by `format_kind`; this reference selects one concrete product `format_options[]` entry. `scope: "publisher"` identifies a publisher-declared catalog option by `{ publisher_domain, format_option_id }`. `scope: "product"` identifies a product-local option by `format_option_id`; the enclosing package/product context supplies the namespace.', + discriminator='scope', + title='Format Option Reference', + ), +] diff --git a/src/adcp/types/generated_poc/core/geo_place_system.py b/src/adcp/types/generated_poc/core/geo_place_system.py index 3011afea0..5fba727fb 100644 --- a/src/adcp/types/generated_poc/core/geo_place_system.py +++ b/src/adcp/types/generated_poc/core/geo_place_system.py @@ -1,11 +1,11 @@ # generated by datamodel-codegen: # filename: core/geo_place_system.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated +from typing import Annotated from pydantic import AnyUrl, Field, RootModel @@ -32,24 +32,16 @@ class GeographicPlaceIdentifierSystem2(RootModel[AnyUrl]): ] -class GeographicPlaceIdentifierSystem( - RootModel[GeographicPlaceIdentifierSystem1 | GeographicPlaceIdentifierSystem2] -): - root: Annotated[ - GeographicPlaceIdentifierSystem1 | GeographicPlaceIdentifierSystem2, - Field( - description='Collision-safe identifier namespace for geographic places. Registered tokens have protocol-defined semantics. Unregistered systems MUST use an absolute HTTPS URI controlled by the catalog owner; consumers compare URI systems as exact opaque strings.', - examples=[ - 'geonames', - 'google_ads', - 'microsoft_ads', - 'https://seller.example/geo/catalogs/places', - ], - title='Geographic Place Identifier System', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +GeographicPlaceIdentifierSystem = Annotated[ + GeographicPlaceIdentifierSystem1 | GeographicPlaceIdentifierSystem2, + Field( + description='Collision-safe identifier namespace for geographic places. Registered tokens have protocol-defined semantics. Unregistered systems MUST use an absolute HTTPS URI controlled by the catalog owner; consumers compare URI systems as exact opaque strings.', + examples=[ + 'geonames', + 'google_ads', + 'microsoft_ads', + 'https://seller.example/geo/catalogs/places', + ], + title='Geographic Place Identifier System', + ), +] diff --git a/src/adcp/types/generated_poc/core/geo_place_type.py b/src/adcp/types/generated_poc/core/geo_place_type.py index f447ce2cd..3bc1f9929 100644 --- a/src/adcp/types/generated_poc/core/geo_place_type.py +++ b/src/adcp/types/generated_poc/core/geo_place_type.py @@ -1,11 +1,11 @@ # generated by datamodel-codegen: # filename: core/geo_place_type.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated +from typing import Annotated from pydantic import AnyUrl, Field, RootModel @@ -49,26 +49,20 @@ class GeographicPlaceType2(RootModel[AnyUrl]): ] -class GeographicPlaceType(RootModel[GeographicPlaceType1 | GeographicPlaceType2]): - root: Annotated[ - GeographicPlaceType1 | GeographicPlaceType2, - Field( - description='Canonical place classification. Registered tokens have protocol-defined meanings. Catalog-specific classifications without a registered mapping MUST use an absolute HTTPS URI controlled by the vocabulary owner; consumers compare URI types as exact opaque strings.', - examples=[ - 'city', - 'municipality', - 'borough', - 'neighborhood', - 'post_town', - 'city_region', - 'county', - 'https://seller.example/geo/place-types/trade-area', - ], - title='Geographic Place Type', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +GeographicPlaceType = Annotated[ + GeographicPlaceType1 | GeographicPlaceType2, + Field( + description='Canonical place classification. Registered tokens have protocol-defined meanings. Catalog-specific classifications without a registered mapping MUST use an absolute HTTPS URI controlled by the vocabulary owner; consumers compare URI types as exact opaque strings.', + examples=[ + 'city', + 'municipality', + 'borough', + 'neighborhood', + 'post_town', + 'city_region', + 'county', + 'https://seller.example/geo/place-types/trade-area', + ], + title='Geographic Place Type', + ), +] diff --git a/src/adcp/types/generated_poc/core/inventory_list_application.py b/src/adcp/types/generated_poc/core/inventory_list_application.py index 389cacd59..80c0d7134 100644 --- a/src/adcp/types/generated_poc/core/inventory_list_application.py +++ b/src/adcp/types/generated_poc/core/inventory_list_application.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/inventory_list_application.json -# timestamp: 2026-08-28T20:03:29+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field class Effect(StrEnum): @@ -138,17 +138,11 @@ class InventoryListApplication2(AdCPBaseModel): ] -class InventoryListApplication(RootModel[InventoryListApplication1 | InventoryListApplication2]): - root: Annotated[ - InventoryListApplication1 | InventoryListApplication2, - Field( - description='Seller-issued, product-scoped receipt showing how one effective property- or collection-list targeting reference intersected the inventory represented by a returned product. Each receipt compares its resolved entries independently against the same product inventory before any effective list is applied; product pricing and forecasts reflect the eligible inventory after all effective lists are composed. Sellers MUST NOT echo auth_token or other separately supplied fetch credentials; the closed receipt shape defines no credential field.', - discriminator='list_type', - title='Inventory List Application', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +InventoryListApplication = Annotated[ + InventoryListApplication1 | InventoryListApplication2, + Field( + description='Seller-issued, product-scoped receipt showing how one effective property- or collection-list targeting reference intersected the inventory represented by a returned product. Each receipt compares its resolved entries independently against the same product inventory before any effective list is applied; product pricing and forecasts reflect the eligible inventory after all effective lists are composed. Sellers MUST NOT echo auth_token or other separately supplied fetch credentials; the closed receipt shape defines no credential field.', + discriminator='list_type', + title='Inventory List Application', + ), +] diff --git a/src/adcp/types/generated_poc/core/localized_creative_asset.py b/src/adcp/types/generated_poc/core/localized_creative_asset.py index 34a9f783a..fe80934dd 100644 --- a/src/adcp/types/generated_poc/core/localized_creative_asset.py +++ b/src/adcp/types/generated_poc/core/localized_creative_asset.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/localized_creative_asset.json -# timestamp: 2026-09-13T20:02:44+00:00 +# timestamp: 2026-10-04T04:53:46+00:00 from __future__ import annotations diff --git a/src/adcp/types/generated_poc/core/macro_bearing_url.py b/src/adcp/types/generated_poc/core/macro_bearing_url.py index ae4510eb0..edadb821a 100644 --- a/src/adcp/types/generated_poc/core/macro_bearing_url.py +++ b/src/adcp/types/generated_poc/core/macro_bearing_url.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/macro_bearing_url.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations from adcp.types._scalar import ScalarStr -from typing import Any, Annotated +from typing import Annotated from pydantic import AnyUrl, Field, RootModel @@ -32,16 +32,10 @@ class MacroBearingUrl4(ScalarStr): } -class MacroBearingUrl(RootModel[str | MacroBearingUrl3 | MacroBearingUrl4]): - root: Annotated[ - str | MacroBearingUrl3 | MacroBearingUrl4, - Field( - description='Backward-compatible URL value that preserves the existing `uri-template` branch (including deep links, protocol-relative references, and RFC 6570 host variables) while additionally accepting absolute HTTP(S) URLs with byte-preserved macro delimiters not legal in a strict URI, including `%%...%%`, `[...]`, and `${...}`. Macro-bearing HTTP(S) URLs require a valid lexical authority and valid percent triplets; an AdCP verifier replaces each recognized token with an unreserved sentinel and validates the resulting absolute URI. Token semantics are established only by `macro_declarations`.', - title='Macro-bearing URL', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +MacroBearingUrl = Annotated[ + str | MacroBearingUrl3 | MacroBearingUrl4, + Field( + description='Backward-compatible URL value that preserves the existing `uri-template` branch (including deep links, protocol-relative references, and RFC 6570 host variables) while additionally accepting absolute HTTP(S) URLs with byte-preserved macro delimiters not legal in a strict URI, including `%%...%%`, `[...]`, and `${...}`. Macro-bearing HTTP(S) URLs require a valid lexical authority and valid percent triplets; an AdCP verifier replaces each recognized token with an unreserved sentinel and validates the resulting absolute URI. Token semantics are established only by `macro_declarations`.', + title='Macro-bearing URL', + ), +] diff --git a/src/adcp/types/generated_poc/core/media_buy_available_action_id.py b/src/adcp/types/generated_poc/core/media_buy_available_action_id.py index e62069a81..d35554f9f 100644 --- a/src/adcp/types/generated_poc/core/media_buy_available_action_id.py +++ b/src/adcp/types/generated_poc/core/media_buy_available_action_id.py @@ -1,12 +1,20 @@ # generated by datamodel-codegen: # filename: core/media_buy_available_action_id.json -# timestamp: 2026-09-14T14:47:21+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from typing import Annotated, Literal +from pydantic import Field + from ..enums import media_buy_valid_action -MediaBuyAvailableActionId = media_buy_valid_action.MediaBuyValidAction | Literal['update_media_buy_frequency_cap'] +MediaBuyAvailableActionId = Annotated[ + media_buy_valid_action.MediaBuyValidAction | Literal['update_media_buy_frequency_cap'], + Field( + description='An action identifier for the structured available_actions surface. It contains every legacy valid_actions value plus structured-only actions introduced after that deprecated flat surface.', + title='Media Buy Available Action ID', + ), +] diff --git a/src/adcp/types/generated_poc/core/missing_metric.py b/src/adcp/types/generated_poc/core/missing_metric.py index 24f45b9ac..a9e5c46ac 100644 --- a/src/adcp/types/generated_poc/core/missing_metric.py +++ b/src/adcp/types/generated_poc/core/missing_metric.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/missing_metric.json -# timestamp: 2026-08-24T04:35:32+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..enums import attribution_methodology as attribution_methodology_1 from ..enums import available_metric @@ -57,17 +57,11 @@ class MissingMetric2(AdCPBaseModel): ] = None -class MissingMetric(RootModel[MissingMetric1 | MissingMetric2]): - root: Annotated[ - MissingMetric1 | MissingMetric2, - Field( - description='One metric the binding reporting contract declared but that is not populated in a delivery report. Symmetric with `committed_metrics` and discriminated by `scope`.', - discriminator='scope', - title='Missing Metric', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +MissingMetric = Annotated[ + MissingMetric1 | MissingMetric2, + Field( + description='One metric the binding reporting contract declared but that is not populated in a delivery report. Symmetric with `committed_metrics` and discriminated by `scope`.', + discriminator='scope', + title='Missing Metric', + ), +] diff --git a/src/adcp/types/generated_poc/core/offering_asset_group.py b/src/adcp/types/generated_poc/core/offering_asset_group.py index 129cda044..cc091b559 100644 --- a/src/adcp/types/generated_poc/core/offering_asset_group.py +++ b/src/adcp/types/generated_poc/core/offering_asset_group.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/offering_asset_group.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-03T00:36:50+00:00 from __future__ import annotations -from typing import Any, Annotated +from typing import Annotated from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..enums import asset_content_type from . import ext as ext_1 @@ -28,44 +28,23 @@ ) -class Items( - RootModel[ - text_asset.TextAsset - | image_asset.ImageAsset - | video_asset.VideoAsset - | audio_asset.AudioAsset - | url_asset.UrlAsset - | html_asset.HtmlAsset - | markdown_asset.MarkdownAsset - | vast_asset.VastAsset - | daast_asset.DaastAsset - | css_asset.CssAsset - | javascript_asset.JavascriptAsset - | zip_asset.ZipAsset - | webhook_asset.WebhookAsset - ] -): - root: Annotated[ - text_asset.TextAsset - | image_asset.ImageAsset - | video_asset.VideoAsset - | audio_asset.AudioAsset - | url_asset.UrlAsset - | html_asset.HtmlAsset - | markdown_asset.MarkdownAsset - | vast_asset.VastAsset - | daast_asset.DaastAsset - | css_asset.CssAsset - | javascript_asset.JavascriptAsset - | zip_asset.ZipAsset - | webhook_asset.WebhookAsset, - Field(discriminator='asset_type'), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +Items = Annotated[ + text_asset.TextAsset + | image_asset.ImageAsset + | video_asset.VideoAsset + | audio_asset.AudioAsset + | url_asset.UrlAsset + | html_asset.HtmlAsset + | markdown_asset.MarkdownAsset + | vast_asset.VastAsset + | daast_asset.DaastAsset + | css_asset.CssAsset + | javascript_asset.JavascriptAsset + | zip_asset.ZipAsset + | webhook_asset.WebhookAsset, + Field(discriminator='asset_type'), +] + class OfferingAssetGroup(AdCPBaseModel): model_config = ConfigDict( diff --git a/src/adcp/types/generated_poc/core/optimization_goal.py b/src/adcp/types/generated_poc/core/optimization_goal.py index 1eb310004..89ea7f28c 100644 --- a/src/adcp/types/generated_poc/core/optimization_goal.py +++ b/src/adcp/types/generated_poc/core/optimization_goal.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/optimization_goal.json -# timestamp: 2026-10-01T07:57:26+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..enums import event_type as event_type_1 from ..enums import reach_unit as reach_unit_1 @@ -298,17 +298,11 @@ class OptimizationGoal10(AdCPBaseModel): ] = None -class OptimizationGoal(RootModel[OptimizationGoal8 | OptimizationGoal9 | OptimizationGoal10]): - root: Annotated[ - OptimizationGoal8 | OptimizationGoal9 | OptimizationGoal10, - Field( - description='A single objective function: what to maximize or optimize, in what units, and in what priority order. Used on packages to optimize delivery within one package and on seller-optimized budget allocations to allocate spend across packages. Currency-bearing execution policy belongs in BiddingPolicy in 3.2. Legacy target.cost_per and target.per_ad_spend remain accepted only on package goals for migration and are deprecated. The primary goal is the earliest array entry among goals with the lowest explicit numeric priority; goals without priority follow all explicitly prioritized goals; when all priorities are omitted, the first entry is primary. This array-order tie-break makes duplicate priorities deterministic.', - discriminator='kind', - title='Optimization Goal', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +OptimizationGoal = Annotated[ + OptimizationGoal8 | OptimizationGoal9 | OptimizationGoal10, + Field( + description='A single objective function: what to maximize or optimize, in what units, and in what priority order. Used on packages to optimize delivery within one package and on seller-optimized budget allocations to allocate spend across packages. Currency-bearing execution policy belongs in BiddingPolicy in 3.2. Legacy target.cost_per and target.per_ad_spend remain accepted only on package goals for migration and are deprecated. The primary goal is the earliest array entry among goals with the lowest explicit numeric priority; goals without priority follow all explicitly prioritized goals; when all priorities are omitted, the first entry is primary. This array-order tie-break makes duplicate priorities deterministic.', + discriminator='kind', + title='Optimization Goal', + ), +] diff --git a/src/adcp/types/generated_poc/core/package_format_snapshot.py b/src/adcp/types/generated_poc/core/package_format_snapshot.py index 86cd1b33a..c90710e9d 100644 --- a/src/adcp/types/generated_poc/core/package_format_snapshot.py +++ b/src/adcp/types/generated_poc/core/package_format_snapshot.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/package_format_snapshot.json -# timestamp: 2026-09-05T19:08:34+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated, Any, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel +from pydantic import AnyUrl, ConfigDict, Field from ..enums import channels, daast_version, vast_version from ..formats.canonical import ( @@ -2433,50 +2433,25 @@ class PackageFormatSnapshot33(PackageFormatSnapshot16): params: Annotated[dict[str, Any], Field(description="Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`.")] -class PackageFormatSnapshot( - RootModel[ - PackageFormatSnapshot18 - | PackageFormatSnapshot19 - | PackageFormatSnapshot20 - | PackageFormatSnapshot21 - | PackageFormatSnapshot22 - | PackageFormatSnapshot23 - | PackageFormatSnapshot24 - | PackageFormatSnapshot25 - | PackageFormatSnapshot26 - | PackageFormatSnapshot27 - | PackageFormatSnapshot28 - | PackageFormatSnapshot29 - | PackageFormatSnapshot30 - | PackageFormatSnapshot31 - | PackageFormatSnapshot32 - | PackageFormatSnapshot33 - ] -): - root: Annotated[ - PackageFormatSnapshot18 - | PackageFormatSnapshot19 - | PackageFormatSnapshot20 - | PackageFormatSnapshot21 - | PackageFormatSnapshot22 - | PackageFormatSnapshot23 - | PackageFormatSnapshot24 - | PackageFormatSnapshot25 - | PackageFormatSnapshot26 - | PackageFormatSnapshot27 - | PackageFormatSnapshot28 - | PackageFormatSnapshot29 - | PackageFormatSnapshot30 - | PackageFormatSnapshot31 - | PackageFormatSnapshot32 - | PackageFormatSnapshot33, - Field( - description="Immutable package-time snapshot of one selected Product format declaration. Product-format fields remain flattened at the root for 3.x compatibility; product, placement, exact execution-version, and digest fields bind the seller's package-time decision. A contract-bearing snapshot is durably identified even when no preview or observation feature is offered.", - title='Package Format Snapshot', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +PackageFormatSnapshot = Annotated[ + PackageFormatSnapshot18 + | PackageFormatSnapshot19 + | PackageFormatSnapshot20 + | PackageFormatSnapshot21 + | PackageFormatSnapshot22 + | PackageFormatSnapshot23 + | PackageFormatSnapshot24 + | PackageFormatSnapshot25 + | PackageFormatSnapshot26 + | PackageFormatSnapshot27 + | PackageFormatSnapshot28 + | PackageFormatSnapshot29 + | PackageFormatSnapshot30 + | PackageFormatSnapshot31 + | PackageFormatSnapshot32 + | PackageFormatSnapshot33, + Field( + description="Immutable package-time snapshot of one selected Product format declaration. Product-format fields remain flattened at the root for 3.x compatibility; product, placement, exact execution-version, and digest fields bind the seller's package-time decision. A contract-bearing snapshot is durably identified even when no preview or observation feature is offered.", + title='Package Format Snapshot', + ), +] diff --git a/src/adcp/types/generated_poc/core/package_signal_targeting.py b/src/adcp/types/generated_poc/core/package_signal_targeting.py index c9679a580..0a5f4a8fd 100644 --- a/src/adcp/types/generated_poc/core/package_signal_targeting.py +++ b/src/adcp/types/generated_poc/core/package_signal_targeting.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/package_signal_targeting.json -# timestamp: 2026-06-05T16:54:33+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from . import activation_key as activation_key_1 from . import signal_ref as signal_ref_1 @@ -162,18 +162,10 @@ class PackageSignalTargeting7(PackageSignalTargeting3, PackageSignalTargeting4): ] = None -class PackageSignalTargeting( - RootModel[PackageSignalTargeting5 | PackageSignalTargeting6 | PackageSignalTargeting7] -): - root: Annotated[ - PackageSignalTargeting5 | PackageSignalTargeting6 | PackageSignalTargeting7, - Field( - description="Buy-time selection of one seller-offered signal inside a package signal targeting group. The signal_ref uses scope 'product' for a product-local signal option, scope 'data_provider' for a signal defined in a data provider's published adagents.json signals[], or scope 'signal_source' for a source-native signal that is not published in adagents.json signals[]. The selected product's inline Product.signal_targeting_options, get_signals feed when inline options are omitted, and signal_targeting_rules define buy-time eligibility. Inclusion and exclusion are controlled by the parent group operator: use operator 'any' to include users matching the signal expression and operator 'none' to exclude users matching the signal expression. For binary signals, value MUST be true; do not use value=false for exclusion inside signal_targeting_groups. Use audience_include/audience_exclude only for buyer-managed first-party audiences registered through sync_audiences.", - title='Package Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +PackageSignalTargeting = Annotated[ + PackageSignalTargeting5 | PackageSignalTargeting6 | PackageSignalTargeting7, + Field( + description="Buy-time selection of one seller-offered signal inside a package signal targeting group. The signal_ref uses scope 'product' for a product-local signal option, scope 'data_provider' for a signal defined in a data provider's published adagents.json signals[], or scope 'signal_source' for a source-native signal that is not published in adagents.json signals[]. The selected product's inline Product.signal_targeting_options, get_signals feed when inline options are omitted, and signal_targeting_rules define buy-time eligibility. Inclusion and exclusion are controlled by the parent group operator: use operator 'any' to include users matching the signal expression and operator 'none' to exclude users matching the signal expression. For binary signals, value MUST be true; do not use value=false for exclusion inside signal_targeting_groups. Use audience_include/audience_exclude only for buyer-managed first-party audiences registered through sync_audiences.", + title='Package Signal Targeting', + ), +] diff --git a/src/adcp/types/generated_poc/core/performance_feedback_metric.py b/src/adcp/types/generated_poc/core/performance_feedback_metric.py index d99907eff..b349e74cc 100644 --- a/src/adcp/types/generated_poc/core/performance_feedback_metric.py +++ b/src/adcp/types/generated_poc/core/performance_feedback_metric.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/performance_feedback_metric.json -# timestamp: 2026-09-05T11:09:57+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..enums import attribution_methodology as attribution_methodology_1 from ..enums import available_metric @@ -62,17 +62,11 @@ class PerformanceFeedbackMetric2(AdCPBaseModel): ] = None -class PerformanceFeedbackMetric(RootModel[PerformanceFeedbackMetric1 | PerformanceFeedbackMetric2]): - root: Annotated[ - PerformanceFeedbackMetric1 | PerformanceFeedbackMetric2, - Field( - description='Metric identity for a performance-feedback assertion. Uses the same standard/vendor discriminator and qualifier vocabulary as committed_metrics, missing_metrics, and package-level delivery values (metric_values or vendor_metric_values).', - discriminator='scope', - title='Performance Feedback Metric', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +PerformanceFeedbackMetric = Annotated[ + PerformanceFeedbackMetric1 | PerformanceFeedbackMetric2, + Field( + description='Metric identity for a performance-feedback assertion. Uses the same standard/vendor discriminator and qualifier vocabulary as committed_metrics, missing_metrics, and package-level delivery values (metric_values or vendor_metric_values).', + discriminator='scope', + title='Performance Feedback Metric', + ), +] diff --git a/src/adcp/types/generated_poc/core/placement_identity.py b/src/adcp/types/generated_poc/core/placement_identity.py index 8e0854451..22bbeca3d 100644 --- a/src/adcp/types/generated_poc/core/placement_identity.py +++ b/src/adcp/types/generated_poc/core/placement_identity.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/placement_identity.json -# timestamp: 2026-08-28T20:03:29+00:00 +# timestamp: 2026-10-04T04:53:46+00:00 from __future__ import annotations diff --git a/src/adcp/types/generated_poc/core/placement_selection.py b/src/adcp/types/generated_poc/core/placement_selection.py index 67e5d3908..911c8ad8e 100644 --- a/src/adcp/types/generated_poc/core/placement_selection.py +++ b/src/adcp/types/generated_poc/core/placement_selection.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/placement_selection.json -# timestamp: 2026-08-28T20:03:29+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from . import ext as ext_1 from . import placement_identity, placement_ref @@ -36,17 +36,11 @@ class PlacementSelection1(AdCPBaseModel): ext: ext_1.ExtensionObject | None = None -class PlacementSelection(RootModel[PlacementSelection1 | PlacementSelection2]): - root: Annotated[ - PlacementSelection1 | PlacementSelection2, - Field( - description="Complete purchased placement set within a product. This is inventory targeting, not creative routing: creative_assignments[].placement_refs route a creative only within this purchased set. Requests use mode selected to name the complete required set or mode default to accept the product/seller default. Across discovery, create, and update, a selected set is satisfied inherently when it exactly equals a fixed product's complete included placement set; this exact restatement does not require overlay_support.placement_selection, while partial selection does. Included placements are never independently selectable. Products with any fixed mode:included placement cannot advertise selectable placement overlay support because the fixed placement would make a partial buyer-selected set incomplete; sellers expose separately selectable inventory when needed. Successful package readback MUST echo the committed selected set. Sellers reject unknown, duplicate, cross-publisher, incompatible, or partially applicable refs rather than silently changing the selection.", - discriminator='mode', - title='Placement Selection', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +PlacementSelection = Annotated[ + PlacementSelection1 | PlacementSelection2, + Field( + description="Complete purchased placement set within a product. This is inventory targeting, not creative routing: creative_assignments[].placement_refs route a creative only within this purchased set. Requests use mode selected to name the complete required set or mode default to accept the product/seller default. Across discovery, create, and update, a selected set is satisfied inherently when it exactly equals a fixed product's complete included placement set; this exact restatement does not require overlay_support.placement_selection, while partial selection does. Included placements are never independently selectable. Products with any fixed mode:included placement cannot advertise selectable placement overlay support because the fixed placement would make a partial buyer-selected set incomplete; sellers expose separately selectable inventory when needed. Successful package readback MUST echo the committed selected set. Sellers reject unknown, duplicate, cross-publisher, incompatible, or partially applicable refs rather than silently changing the selection.", + discriminator='mode', + title='Placement Selection', + ), +] diff --git a/src/adcp/types/generated_poc/core/postal_area.py b/src/adcp/types/generated_poc/core/postal_area.py index 71f9c72f3..b7d6aa29e 100644 --- a/src/adcp/types/generated_poc/core/postal_area.py +++ b/src/adcp/types/generated_poc/core/postal_area.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/postal_area.json -# timestamp: 2026-08-23T19:45:09+00:00 +# timestamp: 2026-10-03T00:35:15+00:00 from __future__ import annotations @@ -415,6 +415,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class PostalArea(RootModel[PostalArea2 | PostalArea1]): root: Annotated[ PostalArea2 | PostalArea1, @@ -442,7 +444,6 @@ def _validate_country_system_pairing(cls, value: Any) -> Any: f"expected one of {list(allowed)!r}" ) return value - def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" if name.startswith('_'): diff --git a/src/adcp/types/generated_poc/core/postal_country_system.py b/src/adcp/types/generated_poc/core/postal_country_system.py index dbda43f16..289c69f07 100644 --- a/src/adcp/types/generated_poc/core/postal_country_system.py +++ b/src/adcp/types/generated_poc/core/postal_country_system.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/postal_country_system.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import Field, RootModel +from pydantic import Field class System(StrEnum): @@ -249,38 +249,19 @@ class PostalCountrySystem10(AdCPBaseModel): ] -class PostalCountrySystem( - RootModel[ - PostalCountrySystem1 - | PostalCountrySystem2 - | PostalCountrySystem3 - | PostalCountrySystem4 - | PostalCountrySystem5 - | PostalCountrySystem6 - | PostalCountrySystem7 - | PostalCountrySystem8 - | PostalCountrySystem9 - | PostalCountrySystem10 - ] -): - root: Annotated[ - PostalCountrySystem1 - | PostalCountrySystem2 - | PostalCountrySystem3 - | PostalCountrySystem4 - | PostalCountrySystem5 - | PostalCountrySystem6 - | PostalCountrySystem7 - | PostalCountrySystem8 - | PostalCountrySystem9 - | PostalCountrySystem10, - Field( - description='Valid country-local postal system pairing. Registered countries only accept their registered local systems; countries without a registered local system use postal_code or custom.', - title='Postal Country System', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +PostalCountrySystem = Annotated[ + PostalCountrySystem1 + | PostalCountrySystem2 + | PostalCountrySystem3 + | PostalCountrySystem4 + | PostalCountrySystem5 + | PostalCountrySystem6 + | PostalCountrySystem7 + | PostalCountrySystem8 + | PostalCountrySystem9 + | PostalCountrySystem10, + Field( + description='Valid country-local postal system pairing. Registered countries only accept their registered local systems; countries without a registered local system use postal_code or custom.', + title='Postal Country System', + ), +] diff --git a/src/adcp/types/generated_poc/core/pricing_option.py b/src/adcp/types/generated_poc/core/pricing_option.py index da60cdb78..adef81621 100644 --- a/src/adcp/types/generated_poc/core/pricing_option.py +++ b/src/adcp/types/generated_poc/core/pricing_option.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/pricing_option.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated +from typing import Annotated -from pydantic import Field, RootModel +from pydantic import Field from ..pricing_options import ( cpa_option, @@ -22,39 +22,20 @@ ) -class PricingOption( - RootModel[ - cpm_option.CpmPricingOption - | vcpm_option.VcpmPricingOption - | cpc_option.CpcPricingOption - | cpcv_option.CpcvPricingOption - | cpv_option.CpvPricingOption - | cpp_option.CppPricingOption - | cpa_option.CpaPricingOption - | revenue_share_option.RevenueSharePricingOption - | flat_rate_option.FlatRatePricingOption - | time_option.TimeBasedPricingOption - ] -): - root: Annotated[ - cpm_option.CpmPricingOption - | vcpm_option.VcpmPricingOption - | cpc_option.CpcPricingOption - | cpcv_option.CpcvPricingOption - | cpv_option.CpvPricingOption - | cpp_option.CppPricingOption - | cpa_option.CpaPricingOption - | revenue_share_option.RevenueSharePricingOption - | flat_rate_option.FlatRatePricingOption - | time_option.TimeBasedPricingOption, - Field( - description="A seller's commercial pricing offer, discriminated by pricing_model. pricing_option_id is the immutable identity of its binding commercial terms: changing fixed price, floor, currency, model, or priced applicability requires a new ID rather than reinterpreting the old one. fixed_price is an agreed seller price; floor_price is the seller minimum for an auction-priced option; price_guidance is non-binding discovery guidance. Buyer execution instructions belong in BiddingPolicy. revenue_share is contingent pricing: its payable spend is calculated from settled commissionable_value and does not use fixed_price or auction bidding fields. The legacy max_bid boolean and package bid_price are deprecated in 3.2 and removed in the next major.", - discriminator='pricing_model', - title='Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +PricingOption = Annotated[ + cpm_option.CpmPricingOption + | vcpm_option.VcpmPricingOption + | cpc_option.CpcPricingOption + | cpcv_option.CpcvPricingOption + | cpv_option.CpvPricingOption + | cpp_option.CppPricingOption + | cpa_option.CpaPricingOption + | revenue_share_option.RevenueSharePricingOption + | flat_rate_option.FlatRatePricingOption + | time_option.TimeBasedPricingOption, + Field( + description="A seller's commercial pricing offer, discriminated by pricing_model. pricing_option_id is the immutable identity of its binding commercial terms: changing fixed price, floor, currency, model, or priced applicability requires a new ID rather than reinterpreting the old one. fixed_price is an agreed seller price; floor_price is the seller minimum for an auction-priced option; price_guidance is non-binding discovery guidance. Buyer execution instructions belong in BiddingPolicy. revenue_share is contingent pricing: its payable spend is calculated from settled commissionable_value and does not use fixed_price or auction bidding fields. The legacy max_bid boolean and package bid_price are deprecated in 3.2 and removed in the next major.", + discriminator='pricing_model', + title='Pricing Option', + ), +] diff --git a/src/adcp/types/generated_poc/core/product.py b/src/adcp/types/generated_poc/core/product.py index 0292d1fca..e36762167 100644 --- a/src/adcp/types/generated_poc/core/product.py +++ b/src/adcp/types/generated_poc/core/product.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/product.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarFloat, ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel, model_validator +from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, model_validator from ..enums import action_source, audio_distribution_type, catalog_type from ..enums import channels as channels_1 @@ -278,13 +278,8 @@ class PublisherProperty87(PublisherProperty83, PublisherProperty84): pass -class PublisherProperty(RootModel[PublisherProperty85 | PublisherProperty86 | PublisherProperty87]): - root: PublisherProperty85 | PublisherProperty86 | PublisherProperty87 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +PublisherProperty = PublisherProperty85 | PublisherProperty86 | PublisherProperty87 + class MetricOptimization(AdCPBaseModel): model_config = ConfigDict( diff --git a/src/adcp/types/generated_poc/core/product_audience_evidence_requirements.py b/src/adcp/types/generated_poc/core/product_audience_evidence_requirements.py index 2bf60994b..cef29250e 100644 --- a/src/adcp/types/generated_poc/core/product_audience_evidence_requirements.py +++ b/src/adcp/types/generated_poc/core/product_audience_evidence_requirements.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/product_audience_evidence_requirements.json -# timestamp: 2026-09-05T19:08:34+00:00 +# timestamp: 2026-10-03T00:36:50+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel +from pydantic import AnyUrl, ConfigDict, Field from ..enums import audience_evidence_methodology, audience_resolution_method, audience_subject_type from . import brand_key @@ -75,20 +75,11 @@ class AcceptedAttestationIssuers3(AdCPBaseModel): ext: ext_1.ExtensionObject | None = None -class AcceptedAttestationIssuers( - RootModel[ - AcceptedAttestationIssuers1 | AcceptedAttestationIssuers2 | AcceptedAttestationIssuers3 - ] -): - root: Annotated[ - AcceptedAttestationIssuers1 | AcceptedAttestationIssuers2 | AcceptedAttestationIssuers3, - Field(discriminator='type'), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AcceptedAttestationIssuers = Annotated[ + AcceptedAttestationIssuers1 | AcceptedAttestationIssuers2 | AcceptedAttestationIssuers3, + Field(discriminator='type'), +] + class ProductAudienceEvidenceRequirements(AdCPBaseModel): model_config = ConfigDict( diff --git a/src/adcp/types/generated_poc/core/product_execution_requirement.py b/src/adcp/types/generated_poc/core/product_execution_requirement.py index 570d79e6b..71063d91a 100644 --- a/src/adcp/types/generated_poc/core/product_execution_requirement.py +++ b/src/adcp/types/generated_poc/core/product_execution_requirement.py @@ -1,15 +1,15 @@ # generated by datamodel-codegen: # filename: core/product_execution_requirement.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..enums import catalog_type, event_type from . import ext as ext_1 @@ -93,22 +93,12 @@ class ProductExecutionRequirement3(AdCPBaseModel): ext: ext_1.ExtensionObject | None = None -class ProductExecutionRequirement( - RootModel[ - ProductExecutionRequirement1 | ProductExecutionRequirement2 | ProductExecutionRequirement3 - ] -): - root: Annotated[ - ProductExecutionRequirement1 | ProductExecutionRequirement2 | ProductExecutionRequirement3, - Field( - description='Experimental. One account resource a package on the product needs before it can be created. `kind` determines the binding: `event_source` via `packages[].optimization_goals[].event_sources[]`, `catalog` via `packages[].catalogs[]`, `downstream_connection` by completing the connection. Account-independent: MUST NOT carry account-specific resource IDs or names. Alternatives inside one requirement are any-of.', - discriminator='kind', - examples=[{'kind': 'catalog', 'catalog_types': ['app']}], - title='Product Execution Requirement', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +ProductExecutionRequirement = Annotated[ + ProductExecutionRequirement1 | ProductExecutionRequirement2 | ProductExecutionRequirement3, + Field( + description='Experimental. One account resource a package on the product needs before it can be created. `kind` determines the binding: `event_source` via `packages[].optimization_goals[].event_sources[]`, `catalog` via `packages[].catalogs[]`, `downstream_connection` by completing the connection. Account-independent: MUST NOT carry account-specific resource IDs or names. Alternatives inside one requirement are any-of.', + discriminator='kind', + examples=[{'kind': 'catalog', 'catalog_types': ['app']}], + title='Product Execution Requirement', + ), +] diff --git a/src/adcp/types/generated_poc/core/product_filters.py b/src/adcp/types/generated_poc/core/product_filters.py index 8798b5dd6..46bef25e9 100644 --- a/src/adcp/types/generated_poc/core/product_filters.py +++ b/src/adcp/types/generated_poc/core/product_filters.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/product_filters.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -10,7 +10,7 @@ from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, ConfigDict, Field, RootModel +from pydantic import AnyUrl, ConfigDict, Field from ..enums import audio_distribution_type, available_metric from ..enums import channels as channels_1 @@ -322,15 +322,8 @@ class SignalTargetingItem7(SignalTargetingItem3, SignalTargetingItem4): ] = TargetingMode.include -class SignalTargetingItem( - RootModel[SignalTargetingItem5 | SignalTargetingItem6 | SignalTargetingItem7] -): - root: SignalTargetingItem5 | SignalTargetingItem6 | SignalTargetingItem7 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +SignalTargetingItem = SignalTargetingItem5 | SignalTargetingItem6 | SignalTargetingItem7 + class TravelTime(AdCPBaseModel): model_config = ConfigDict( diff --git a/src/adcp/types/generated_poc/core/product_format_declaration.py b/src/adcp/types/generated_poc/core/product_format_declaration.py index c5f36d0b5..06bc40f5d 100644 --- a/src/adcp/types/generated_poc/core/product_format_declaration.py +++ b/src/adcp/types/generated_poc/core/product_format_declaration.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/product_format_declaration.json -# timestamp: 2026-09-05T19:08:34+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated, Any, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, Field, RootModel +from pydantic import AnyUrl, Field from ..enums import channels from ..formats.canonical import ( @@ -1611,165 +1611,140 @@ class ProductFormatDeclaration16(AdCPBaseModel): ] -class ProductFormatDeclaration( - RootModel[ - ProductFormatDeclaration1 - | ProductFormatDeclaration2 - | ProductFormatDeclaration3 - | ProductFormatDeclaration4 - | ProductFormatDeclaration5 - | ProductFormatDeclaration6 - | ProductFormatDeclaration7 - | ProductFormatDeclaration8 - | ProductFormatDeclaration9 - | ProductFormatDeclaration10 - | ProductFormatDeclaration11 - | ProductFormatDeclaration12 - | ProductFormatDeclaration13 - | ProductFormatDeclaration14 - | ProductFormatDeclaration15 - | ProductFormatDeclaration16 - ] -): - root: Annotated[ - ProductFormatDeclaration1 - | ProductFormatDeclaration2 - | ProductFormatDeclaration3 - | ProductFormatDeclaration4 - | ProductFormatDeclaration5 - | ProductFormatDeclaration6 - | ProductFormatDeclaration7 - | ProductFormatDeclaration8 - | ProductFormatDeclaration9 - | ProductFormatDeclaration10 - | ProductFormatDeclaration11 - | ProductFormatDeclaration12 - | ProductFormatDeclaration13 - | ProductFormatDeclaration14 - | ProductFormatDeclaration15 - | ProductFormatDeclaration16, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries only the canonical creative-shape parameters (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Processing metadata such as `macro_resolution_capabilities` belongs at this declaration\'s top level rather than inside `params`. Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). Canonical product-bound declarations are the AdCP 3.2 authoring path; v1 named formats remain only as deprecated 3.x compatibility projections.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 15 canonicals (branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, +ProductFormatDeclaration = Annotated[ + ProductFormatDeclaration1 + | ProductFormatDeclaration2 + | ProductFormatDeclaration3 + | ProductFormatDeclaration4 + | ProductFormatDeclaration5 + | ProductFormatDeclaration6 + | ProductFormatDeclaration7 + | ProductFormatDeclaration8 + | ProductFormatDeclaration9 + | ProductFormatDeclaration10 + | ProductFormatDeclaration11 + | ProductFormatDeclaration12 + | ProductFormatDeclaration13 + | ProductFormatDeclaration14 + | ProductFormatDeclaration15 + | ProductFormatDeclaration16, + Field( + description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries only the canonical creative-shape parameters (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Processing metadata such as `macro_resolution_capabilities` belongs at this declaration\'s top level rather than inside `params`. Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). Canonical product-bound declarations are the AdCP 3.2 authoring path; v1 named formats remain only as deprecated 3.x compatibility projections.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 15 canonicals (branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', + discriminator='format_kind', + examples=[ + { + 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', + 'data': { + 'format_kind': 'video_hosted', + 'params': { + 'orientation': 'vertical', + 'aspect_ratio': '9:16', + 'duration_ms_range': [3000, 90000], + 'min_width': 1080, + 'min_height': 1920, + 'max_file_size_mb': 200, + 'video_codecs': ['h264'], + 'audio_codecs': ['aac'], + 'headline_max_chars': 25, + 'primary_text_max_chars': 72, + 'captions': 'recommended', + 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], + 'composition_model': 'deterministic', + 'platform_extensions': [ + { + 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', + 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', + } + ], }, }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, + }, + { + 'description': 'IAB Medium Rectangle (300x250) — narrows image', + 'data': { + 'format_kind': 'image', + 'params': { + 'width': 300, + 'height': 250, + 'max_file_size_kb': 200, + 'image_formats': ['jpg', 'png', 'gif'], + 'ssl_required': True, + 'composition_model': 'deterministic', + 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], }, }, - { - 'description': 'Québec French-only display image — same image shape with a seller-enforced creative locale constraint', - 'data': { - 'format_kind': 'image', - 'format_option_id': 'quebec_display_image', - 'display_name': 'Québec display image — French creative', - 'canonical_formats_only': True, - 'locale_policy': {'accepted_language_ranges': ['fr']}, - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png'], - }, + }, + { + 'description': 'Québec French-only display image — same image shape with a seller-enforced creative locale constraint', + 'data': { + 'format_kind': 'image', + 'format_option_id': 'quebec_display_image', + 'display_name': 'Québec display image — French creative', + 'canonical_formats_only': True, + 'locale_policy': {'accepted_language_ranges': ['fr']}, + 'params': { + 'width': 300, + 'height': 250, + 'max_file_size_kb': 200, + 'image_formats': ['jpg', 'png'], }, }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, + }, + { + 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", + 'data': { + 'format_kind': 'audio_hosted', + 'params': { + 'duration_ms_exact': 30000, + 'audio_codecs': ['mp3', 'aac'], + 'audio_sample_rates': [44100, 48000], + 'audio_channels': ['stereo'], + 'loudness_lufs': -16, + 'asset_source': 'publisher_host_recorded', + 'buyer_asset_acceptance': 'rejected', + 'composition_model': 'deterministic', + 'slots': [ + { + 'asset_group_id': 'script', + 'required': True, + 'asset_type': 'text', + 'max_chars': 800, + }, + { + 'asset_group_id': 'offering_ref', + 'required': False, + 'asset_type': 'text', + }, + ], + 'production_window_business_days': 7, }, }, - { - 'description': "StreamHaus Branded Feature — a still-custom branded-content format with format_schema pointing at the fictional seller's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations because no v1 named format expresses this bespoke composition.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'branded_content', - 'format_schema': { - 'uri': 'https://ads.streamhaus.example/schemas/formats/branded_feature_v1', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'streamhaus_branded_feature', - 'display_name': 'StreamHaus branded feature', - 'applies_to_channels': ['display'], - 'params': { - 'components': [ - {'placement_type': 'feature_header', 'required': True}, - {'placement_type': 'sponsored_story_card', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 12, - 'ssl_required': True, - }, + }, + { + 'description': "StreamHaus Branded Feature — a still-custom branded-content format with format_schema pointing at the fictional seller's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations because no v1 named format expresses this bespoke composition.", + 'data': { + 'format_kind': 'custom', + 'canonical_formats_only': True, + 'format_shape': 'branded_content', + 'format_schema': { + 'uri': 'https://ads.streamhaus.example/schemas/formats/branded_feature_v1', + 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', + }, + 'format_option_id': 'streamhaus_branded_feature', + 'display_name': 'StreamHaus branded feature', + 'applies_to_channels': ['display'], + 'params': { + 'components': [ + {'placement_type': 'feature_header', 'required': True}, + {'placement_type': 'sponsored_story_card', 'required': True}, + {'placement_type': 'sponsorship_lockup', 'required': True}, + ], + 'exclusivity_window_hours': 12, + 'ssl_required': True, }, }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) + }, + ], + title='Product Format Declaration', + ), +] diff --git a/src/adcp/types/generated_poc/core/publisher_property_selector.py b/src/adcp/types/generated_poc/core/publisher_property_selector.py index 353653ba6..5b4cc5862 100644 --- a/src/adcp/types/generated_poc/core/publisher_property_selector.py +++ b/src/adcp/types/generated_poc/core/publisher_property_selector.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/publisher_property_selector.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from . import property_id, property_tag @@ -97,19 +97,11 @@ class PublisherPropertySelector3(AdCPBaseModel): ] -class PublisherPropertySelector( - RootModel[PublisherPropertySelector1 | PublisherPropertySelector2 | PublisherPropertySelector3] -): - root: Annotated[ - PublisherPropertySelector1 | PublisherPropertySelector2 | PublisherPropertySelector3, - Field( - description="Selects properties from a publisher's adagents.json. Used for both product definitions and agent authorization. Supports three selection patterns: all properties, specific IDs, or by tags. Each selector targets one publisher via `publisher_domain` (string) or a fan-out across many publishers that share the same selector via `publisher_domains` (array). Exactly one of `publisher_domain` or `publisher_domains` MUST be present. When `publisher_domains` is used, the selector is logically equivalent to repeating the same entry once per listed domain.", - discriminator='selection_type', - title='Publisher Property Selector', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +PublisherPropertySelector = Annotated[ + PublisherPropertySelector1 | PublisherPropertySelector2 | PublisherPropertySelector3, + Field( + description="Selects properties from a publisher's adagents.json. Used for both product definitions and agent authorization. Supports three selection patterns: all properties, specific IDs, or by tags. Each selector targets one publisher via `publisher_domain` (string) or a fan-out across many publishers that share the same selector via `publisher_domains` (array). Exactly one of `publisher_domain` or `publisher_domains` MUST be present. When `publisher_domains` is used, the selector is logically equivalent to repeating the same entry once per listed domain.", + discriminator='selection_type', + title='Publisher Property Selector', + ), +] diff --git a/src/adcp/types/generated_poc/core/registry_event.py b/src/adcp/types/generated_poc/core/registry_event.py index 1be89ea76..ae98b5370 100644 --- a/src/adcp/types/generated_poc/core/registry_event.py +++ b/src/adcp/types/generated_poc/core/registry_event.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/registry_event.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -1316,59 +1316,30 @@ class RegistryEvent9(AdCPBaseModel): ] -class RegistryEvent( - RootModel[ - RegistryEvent1 - | RegistryEvent2 - | RegistryEvent3 - | RegistryEvent4 - | RegistryEvent5 - | RegistryEvent6 - | RegistryEvent7 - | RegistryEvent8 - | RegistryEvent9 - | RegistryEvent10 - | RegistryEvent11 - | RegistryEvent12 - | RegistryEvent13 - | RegistryEvent14 - | RegistryEvent15 - | RegistryEvent16 - | RegistryEvent17 - | RegistryEvent18 - | RegistryEvent19 - | RegistryEvent20 - ] -): - root: Annotated[ - RegistryEvent1 - | RegistryEvent2 - | RegistryEvent3 - | RegistryEvent4 - | RegistryEvent5 - | RegistryEvent6 - | RegistryEvent7 - | RegistryEvent8 - | RegistryEvent9 - | RegistryEvent10 - | RegistryEvent11 - | RegistryEvent12 - | RegistryEvent13 - | RegistryEvent14 - | RegistryEvent15 - | RegistryEvent16 - | RegistryEvent17 - | RegistryEvent18 - | RegistryEvent19 - | RegistryEvent20, - Field( - description='A single cursor-ordered event from the AgenticAdvertising.org registry change feed. Events are denormalized so RegistrySync clients can update local property, agent, publisher, and authorization indexes without re-reading the full registry. The discriminator is event_type; each branch defines the event-specific payload shape. See specs/registry-change-feed.md.', - discriminator='event_type', - title='Registry Event', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +RegistryEvent = Annotated[ + RegistryEvent1 + | RegistryEvent2 + | RegistryEvent3 + | RegistryEvent4 + | RegistryEvent5 + | RegistryEvent6 + | RegistryEvent7 + | RegistryEvent8 + | RegistryEvent9 + | RegistryEvent10 + | RegistryEvent11 + | RegistryEvent12 + | RegistryEvent13 + | RegistryEvent14 + | RegistryEvent15 + | RegistryEvent16 + | RegistryEvent17 + | RegistryEvent18 + | RegistryEvent19 + | RegistryEvent20, + Field( + description='A single cursor-ordered event from the AgenticAdvertising.org registry change feed. Events are denormalized so RegistrySync clients can update local property, agent, publisher, and authorization indexes without re-reading the full registry. The discriminator is event_type; each branch defines the event-specific payload shape. See specs/registry-change-feed.md.', + discriminator='event_type', + title='Registry Event', + ), +] diff --git a/src/adcp/types/generated_poc/core/reporting_control_total.py b/src/adcp/types/generated_poc/core/reporting_control_total.py index ea9d5e1ba..fdbd69365 100644 --- a/src/adcp/types/generated_poc/core/reporting_control_total.py +++ b/src/adcp/types/generated_poc/core/reporting_control_total.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/reporting_control_total.json -# timestamp: 2026-09-01T19:37:00+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field class ReportingControlTotal1(AdCPBaseModel): @@ -60,16 +60,10 @@ class ReportingControlTotal2(AdCPBaseModel): ] = None -class ReportingControlTotal(RootModel[ReportingControlTotal1 | ReportingControlTotal2]): - root: Annotated[ - ReportingControlTotal1 | ReportingControlTotal2, - Field( - description='One profile-defined aggregate used to reconcile a reporting revision without rereading every row. Names and units are defined by the immutable report definition. Values use canonical strings so currency and large integer comparisons are exact across SDKs.', - title='Reporting Control Total', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +ReportingControlTotal = Annotated[ + ReportingControlTotal1 | ReportingControlTotal2, + Field( + description='One profile-defined aggregate used to reconcile a reporting revision without rereading every row. Names and units are defined by the immutable report definition. Values use canonical strings so currency and large integer comparisons are exact across SDKs.', + title='Reporting Control Total', + ), +] diff --git a/src/adcp/types/generated_poc/core/reporting_dataset_share_destination.py b/src/adcp/types/generated_poc/core/reporting_dataset_share_destination.py index a67e0c7cd..c67138f1b 100644 --- a/src/adcp/types/generated_poc/core/reporting_dataset_share_destination.py +++ b/src/adcp/types/generated_poc/core/reporting_dataset_share_destination.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/reporting_dataset_share_destination.json -# timestamp: 2026-09-01T19:37:00+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field class ReportingDatasetShareDestination1(AdCPBaseModel): @@ -76,18 +76,10 @@ class ReportingDatasetShareDestination2(AdCPBaseModel): ] -class ReportingDatasetShareDestination( - RootModel[ReportingDatasetShareDestination1 | ReportingDatasetShareDestination2] -): - root: Annotated[ - ReportingDatasetShareDestination1 | ReportingDatasetShareDestination2, - Field( - description="Recipient configuration for a producer-hosted reporting share. The caller either references an existing seller-issued immutable recipient/destination generation or asks the seller to provision one for the named recipient. A destination_ref is owned by the stable authenticated principal's relationship with this seller and may be reused across accounts; each account delivery configuration separately authorizes disclosure of its feed and scope. Changing proof-bound recipient coordinates or the accepted delivery contract produces a new destination_ref. No bearer profile, token, private key, password, or other credential may appear here.", - title='Reporting Dataset Share Destination', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +ReportingDatasetShareDestination = Annotated[ + ReportingDatasetShareDestination1 | ReportingDatasetShareDestination2, + Field( + description="Recipient configuration for a producer-hosted reporting share. The caller either references an existing seller-issued immutable recipient/destination generation or asks the seller to provision one for the named recipient. A destination_ref is owned by the stable authenticated principal's relationship with this seller and may be reused across accounts; each account delivery configuration separately authorizes disclosure of its feed and scope. Changing proof-bound recipient coordinates or the accepted delivery contract produces a new destination_ref. No bearer profile, token, private key, password, or other credential may appear here.", + title='Reporting Dataset Share Destination', + ), +] diff --git a/src/adcp/types/generated_poc/core/reporting_delivery_method.py b/src/adcp/types/generated_poc/core/reporting_delivery_method.py index 635432a6b..ab901db2a 100644 --- a/src/adcp/types/generated_poc/core/reporting_delivery_method.py +++ b/src/adcp/types/generated_poc/core/reporting_delivery_method.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/reporting_delivery_method.json -# timestamp: 2026-09-01T19:37:00+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from . import reporting_dataset_share_destination, reporting_write_destination @@ -94,18 +94,10 @@ class ReportingDeliveryMethod3(AdCPBaseModel): destination: reporting_write_destination.ReportingWriteDestination -class ReportingDeliveryMethod( - RootModel[ReportingDeliveryMethod1 | ReportingDeliveryMethod2 | ReportingDeliveryMethod3] -): - root: Annotated[ - ReportingDeliveryMethod1 | ReportingDeliveryMethod2 | ReportingDeliveryMethod3, - Field( - description='Provider-neutral durable reporting delivery method. The caller may request protocol-managed provisioning or reuse an existing seller-issued binding. Transport names are open so new platforms do not require an AdCP enum change. Credentials, bearer profiles, and private keys MUST NOT appear. Sellers implementing this schema MUST advertise media_buy.reporting_delivery in experimental_features.', - title='Reporting Delivery Method', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +ReportingDeliveryMethod = Annotated[ + ReportingDeliveryMethod1 | ReportingDeliveryMethod2 | ReportingDeliveryMethod3, + Field( + description='Provider-neutral durable reporting delivery method. The caller may request protocol-managed provisioning or reuse an existing seller-issued binding. Transport names are open so new platforms do not require an AdCP enum change. Credentials, bearer profiles, and private keys MUST NOT appear. Sellers implementing this schema MUST advertise media_buy.reporting_delivery in experimental_features.', + title='Reporting Delivery Method', + ), +] diff --git a/src/adcp/types/generated_poc/core/reporting_write_destination.py b/src/adcp/types/generated_poc/core/reporting_write_destination.py index a260aec69..e14248287 100644 --- a/src/adcp/types/generated_poc/core/reporting_write_destination.py +++ b/src/adcp/types/generated_poc/core/reporting_write_destination.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/reporting_write_destination.json -# timestamp: 2026-09-01T19:37:00+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field class ReportingWriteDestination1(AdCPBaseModel): @@ -59,16 +59,10 @@ class ReportingWriteDestination2(AdCPBaseModel): ] = None -class ReportingWriteDestination(RootModel[ReportingWriteDestination1 | ReportingWriteDestination2]): - root: Annotated[ - ReportingWriteDestination1 | ReportingWriteDestination2, - Field( - description="Storage or warehouse destination for durable reporting. The caller either references an existing seller-issued immutable destination generation or asks the seller to validate and bind a provider-native location. A destination_ref is owned by the stable authenticated principal's relationship with this seller and may be reused across accounts; each account delivery configuration separately authorizes its feed and scope. Changing proof-bound coordinates or the accepted delivery contract produces a new destination_ref. Access grants name advertised producer identities; credentials never transit AdCP.", - title='Reporting Write Destination', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +ReportingWriteDestination = Annotated[ + ReportingWriteDestination1 | ReportingWriteDestination2, + Field( + description="Storage or warehouse destination for durable reporting. The caller either references an existing seller-issued immutable destination generation or asks the seller to validate and bind a provider-native location. A destination_ref is owned by the stable authenticated principal's relationship with this seller and may be reused across accounts; each account delivery configuration separately authorizes its feed and scope. Changing proof-bound coordinates or the accepted delivery contract produces a new destination_ref. Access grants name advertised producer identities; credentials never transit AdCP.", + title='Reporting Write Destination', + ), +] diff --git a/src/adcp/types/generated_poc/core/requirements/asset_requirements.py b/src/adcp/types/generated_poc/core/requirements/asset_requirements.py index 75f4c24e8..13a6f7d3d 100644 --- a/src/adcp/types/generated_poc/core/requirements/asset_requirements.py +++ b/src/adcp/types/generated_poc/core/requirements/asset_requirements.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: core/requirements/asset_requirements.json -# timestamp: 2026-06-01T00:32:59+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated +from typing import Annotated -from pydantic import Field, RootModel +from pydantic import Field from . import ( audio_asset_requirements, @@ -24,42 +24,21 @@ ) -class AssetRequirements( - RootModel[ - image_asset_requirements.ImageAssetRequirements - | video_asset_requirements.VideoAssetRequirements - | audio_asset_requirements.AudioAssetRequirements - | text_asset_requirements.TextAssetRequirements - | markdown_asset_requirements.MarkdownAssetRequirements - | html_asset_requirements.HtmlAssetRequirements - | css_asset_requirements.CssAssetRequirements - | javascript_asset_requirements.JavascriptAssetRequirements - | vast_asset_requirements.VastAssetRequirements - | daast_asset_requirements.DaastAssetRequirements - | url_asset_requirements.UrlAssetRequirements - | webhook_asset_requirements.WebhookAssetRequirements - ] -): - root: Annotated[ - image_asset_requirements.ImageAssetRequirements - | video_asset_requirements.VideoAssetRequirements - | audio_asset_requirements.AudioAssetRequirements - | text_asset_requirements.TextAssetRequirements - | markdown_asset_requirements.MarkdownAssetRequirements - | html_asset_requirements.HtmlAssetRequirements - | css_asset_requirements.CssAssetRequirements - | javascript_asset_requirements.JavascriptAssetRequirements - | vast_asset_requirements.VastAssetRequirements - | daast_asset_requirements.DaastAssetRequirements - | url_asset_requirements.UrlAssetRequirements - | webhook_asset_requirements.WebhookAssetRequirements, - Field( - description='Technical requirements for creative assets. The applicable schema is determined by the sibling asset_type field.', - title='Asset Requirements', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AssetRequirements = Annotated[ + image_asset_requirements.ImageAssetRequirements + | video_asset_requirements.VideoAssetRequirements + | audio_asset_requirements.AudioAssetRequirements + | text_asset_requirements.TextAssetRequirements + | markdown_asset_requirements.MarkdownAssetRequirements + | html_asset_requirements.HtmlAssetRequirements + | css_asset_requirements.CssAssetRequirements + | javascript_asset_requirements.JavascriptAssetRequirements + | vast_asset_requirements.VastAssetRequirements + | daast_asset_requirements.DaastAssetRequirements + | url_asset_requirements.UrlAssetRequirements + | webhook_asset_requirements.WebhookAssetRequirements, + Field( + description='Technical requirements for creative assets. The applicable schema is determined by the sibling asset_type field.', + title='Asset Requirements', + ), +] diff --git a/src/adcp/types/generated_poc/core/requirements/catalog_field_binding.py b/src/adcp/types/generated_poc/core/requirements/catalog_field_binding.py index 874e76ccb..c3d6bfcd6 100644 --- a/src/adcp/types/generated_poc/core/requirements/catalog_field_binding.py +++ b/src/adcp/types/generated_poc/core/requirements/catalog_field_binding.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/requirements/catalog_field_binding.json -# timestamp: 2026-06-01T00:32:59+00:00 +# timestamp: 2026-10-03T00:36:50+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from .. import ext as ext_1 @@ -52,13 +52,11 @@ class AssetPoolBinding(AdCPBaseModel): ext: ext_1.ExtensionObject | None = None -class PerItemBindings(RootModel[ScalarBinding | AssetPoolBinding]): - root: Annotated[ScalarBinding | AssetPoolBinding, Field(discriminator='kind')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +PerItemBindings = Annotated[ + ScalarBinding | AssetPoolBinding, + Field(discriminator='kind'), +] + class CatalogFieldBinding1(AdCPBaseModel): model_config = ConfigDict( @@ -85,68 +83,62 @@ class CatalogFieldBinding1(AdCPBaseModel): ext: ext_1.ExtensionObject | None = None -class CatalogFieldBinding(RootModel[ScalarBinding | AssetPoolBinding | CatalogFieldBinding1]): - root: Annotated[ - ScalarBinding | AssetPoolBinding | CatalogFieldBinding1, - Field( - description="Maps a format template slot to a catalog item field or typed asset pool. The 'kind' field identifies the binding variant. All bindings are optional — agents can still infer mappings without them.", - discriminator='kind', - examples=[ - { - 'description': 'Scalar binding — hotel name to headline slot', - 'data': {'kind': 'scalar', 'asset_id': 'headline', 'catalog_field': 'name'}, - }, - { - 'description': 'Scalar binding — nested field (nightly rate)', - 'data': { - 'kind': 'scalar', - 'asset_id': 'price_badge', - 'catalog_field': 'price.amount', - }, +CatalogFieldBinding = Annotated[ + ScalarBinding | AssetPoolBinding | CatalogFieldBinding1, + Field( + description="Maps a format template slot to a catalog item field or typed asset pool. The 'kind' field identifies the binding variant. All bindings are optional — agents can still infer mappings without them.", + discriminator='kind', + examples=[ + { + 'description': 'Scalar binding — hotel name to headline slot', + 'data': {'kind': 'scalar', 'asset_id': 'headline', 'catalog_field': 'name'}, + }, + { + 'description': 'Scalar binding — nested field (nightly rate)', + 'data': { + 'kind': 'scalar', + 'asset_id': 'price_badge', + 'catalog_field': 'price.amount', }, - { - 'description': 'Asset pool binding — hero image from landscape pool', - 'data': { - 'kind': 'asset_pool', - 'asset_id': 'hero_image', - 'asset_group_id': 'images_landscape', - }, + }, + { + 'description': 'Asset pool binding — hero image from landscape pool', + 'data': { + 'kind': 'asset_pool', + 'asset_id': 'hero_image', + 'asset_group_id': 'images_landscape', }, - { - 'description': 'Asset pool binding — Snap vertical background from vertical pool', - 'data': { - 'kind': 'asset_pool', - 'asset_id': 'snap_background', - 'asset_group_id': 'images_vertical', - }, + }, + { + 'description': 'Asset pool binding — Snap vertical background from vertical pool', + 'data': { + 'kind': 'asset_pool', + 'asset_id': 'snap_background', + 'asset_group_id': 'images_vertical', }, - { - 'description': 'Catalog group binding — carousel where each slide is one hotel', - 'data': { - 'kind': 'catalog_group', - 'format_group_id': 'slide', - 'catalog_item': True, - 'per_item_bindings': [ - {'kind': 'scalar', 'asset_id': 'title', 'catalog_field': 'name'}, - { - 'kind': 'scalar', - 'asset_id': 'price', - 'catalog_field': 'price.amount', - }, - { - 'kind': 'asset_pool', - 'asset_id': 'image', - 'asset_group_id': 'images_landscape', - }, - ], - }, + }, + { + 'description': 'Catalog group binding — carousel where each slide is one hotel', + 'data': { + 'kind': 'catalog_group', + 'format_group_id': 'slide', + 'catalog_item': True, + 'per_item_bindings': [ + {'kind': 'scalar', 'asset_id': 'title', 'catalog_field': 'name'}, + { + 'kind': 'scalar', + 'asset_id': 'price', + 'catalog_field': 'price.amount', + }, + { + 'kind': 'asset_pool', + 'asset_id': 'image', + 'asset_group_id': 'images_landscape', + }, + ], }, - ], - title='Catalog Field Binding', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) + }, + ], + title='Catalog Field Binding', + ), +] diff --git a/src/adcp/types/generated_poc/core/signal_id.py b/src/adcp/types/generated_poc/core/signal_id.py index 4d236ce2e..85041b060 100644 --- a/src/adcp/types/generated_poc/core/signal_id.py +++ b/src/adcp/types/generated_poc/core/signal_id.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/signal_id.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel +from pydantic import AnyUrl, ConfigDict, Field class SignalId8(AdCPBaseModel): @@ -61,17 +61,11 @@ class SignalId9(AdCPBaseModel): ] -class SignalId(RootModel[SignalId8 | SignalId9]): - root: Annotated[ - SignalId8 | SignalId9, - Field( - description="DEPRECATED. Use SignalRef for new discovery, activation, and media-buy signal identity surfaces. Legacy universal signal identifier used by older Signals Protocol clients. Uses 'source' as discriminator: 'catalog' for signals from a data provider's published catalog (verifiable), or 'agent' for signals native to a signal source identified by agent_url.", - discriminator='source', - title='Signal ID', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +SignalId = Annotated[ + SignalId8 | SignalId9, + Field( + description="DEPRECATED. Use SignalRef for new discovery, activation, and media-buy signal identity surfaces. Legacy universal signal identifier used by older Signals Protocol clients. Uses 'source' as discriminator: 'catalog' for signals from a data provider's published catalog (verifiable), or 'agent' for signals native to a signal source identified by agent_url.", + discriminator='source', + title='Signal ID', + ), +] diff --git a/src/adcp/types/generated_poc/core/signal_pricing.py b/src/adcp/types/generated_poc/core/signal_pricing.py index fa7366e7e..6d4e08c2b 100644 --- a/src/adcp/types/generated_poc/core/signal_pricing.py +++ b/src/adcp/types/generated_poc/core/signal_pricing.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: core/signal_pricing.json -# timestamp: 2026-06-01T00:32:59+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from . import ext as ext_1 @@ -120,19 +120,11 @@ class VendorPricing5(AdCPBaseModel): ext: ext_1.ExtensionObject | None = None -class VendorPricing( - RootModel[VendorPricing1 | VendorPricing2 | VendorPricing3 | VendorPricing4 | VendorPricing5] -): - root: Annotated[ - VendorPricing1 | VendorPricing2 | VendorPricing3 | VendorPricing4 | VendorPricing5, - Field( - description="Pricing model for a vendor service. Discriminated by model: 'cpm' (fixed CPM), 'percent_of_media' (percentage of spend with optional CPM cap), 'flat_fee' (fixed charge per reporting period), 'per_unit' (fixed price per unit of work), or 'custom' (escape hatch for models not covered by the enumerated forms — requires a description and structured metadata).", - discriminator='model', - title='Vendor Pricing', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +VendorPricing = Annotated[ + VendorPricing1 | VendorPricing2 | VendorPricing3 | VendorPricing4 | VendorPricing5, + Field( + description="Pricing model for a vendor service. Discriminated by model: 'cpm' (fixed CPM), 'percent_of_media' (percentage of spend with optional CPM cap), 'flat_fee' (fixed charge per reporting period), 'per_unit' (fixed price per unit of work), or 'custom' (escape hatch for models not covered by the enumerated forms — requires a description and structured metadata).", + discriminator='model', + title='Vendor Pricing', + ), +] diff --git a/src/adcp/types/generated_poc/core/signal_ref.py b/src/adcp/types/generated_poc/core/signal_ref.py index 469c07b5f..888ba70ba 100644 --- a/src/adcp/types/generated_poc/core/signal_ref.py +++ b/src/adcp/types/generated_poc/core/signal_ref.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/signal_ref.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel +from pydantic import AnyUrl, ConfigDict, Field class SignalRef1(AdCPBaseModel): @@ -77,17 +77,11 @@ class SignalRef3(AdCPBaseModel): ] -class SignalRef(RootModel[SignalRef1 | SignalRef2 | SignalRef3]): - root: Annotated[ - SignalRef1 | SignalRef2 | SignalRef3, - Field( - description="Reference to a named signal definition. Uses scope as discriminator: 'data_provider' for a signal resolved through published adagents.json signals[], 'signal_source' for a source-native signal resolved through the issuing signal source, or 'product' for a product-local signal option. Scope is the resolution path, not provenance; authoritative enrichment lives on the seller, signal source, or data-provider signal definition, not on this reference.", - discriminator='scope', - title='Signal Ref', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +SignalRef = Annotated[ + SignalRef1 | SignalRef2 | SignalRef3, + Field( + description="Reference to a named signal definition. Uses scope as discriminator: 'data_provider' for a signal resolved through published adagents.json signals[], 'signal_source' for a source-native signal resolved through the issuing signal source, or 'product' for a product-local signal option. Scope is the resolution path, not provenance; authoritative enrichment lives on the seller, signal source, or data-provider signal definition, not on this reference.", + discriminator='scope', + title='Signal Ref', + ), +] diff --git a/src/adcp/types/generated_poc/core/signal_targeting.py b/src/adcp/types/generated_poc/core/signal_targeting.py index 5124d3d30..b204df8b0 100644 --- a/src/adcp/types/generated_poc/core/signal_targeting.py +++ b/src/adcp/types/generated_poc/core/signal_targeting.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/signal_targeting.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from . import signal_id as signal_id_1 from . import signal_ref as signal_ref_1 @@ -96,17 +96,11 @@ class SignalTargeting3(AdCPBaseModel): ] = None -class SignalTargeting(RootModel[SignalTargeting1 | SignalTargeting2 | SignalTargeting3]): - root: Annotated[ - SignalTargeting1 | SignalTargeting2 | SignalTargeting3, - Field( - description='Targeting constraint for a specific signal. Uses value_type as discriminator to determine the targeting expression format.', - discriminator='value_type', - title='Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +SignalTargeting = Annotated[ + SignalTargeting1 | SignalTargeting2 | SignalTargeting3, + Field( + description='Targeting constraint for a specific signal. Uses value_type as discriminator to determine the targeting expression format.', + discriminator='value_type', + title='Signal Targeting', + ), +] diff --git a/src/adcp/types/generated_poc/core/signal_targeting_expression.py b/src/adcp/types/generated_poc/core/signal_targeting_expression.py index d6c8979d5..3c823257d 100644 --- a/src/adcp/types/generated_poc/core/signal_targeting_expression.py +++ b/src/adcp/types/generated_poc/core/signal_targeting_expression.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/signal_targeting_expression.json -# timestamp: 2026-06-01T00:32:59+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from . import signal_ref as signal_ref_1 @@ -65,19 +65,11 @@ class SignalTargetingExpression3(AdCPBaseModel): ] = None -class SignalTargetingExpression( - RootModel[SignalTargetingExpression1 | SignalTargetingExpression2 | SignalTargetingExpression3] -): - root: Annotated[ - SignalTargetingExpression1 | SignalTargetingExpression2 | SignalTargetingExpression3, - Field( - description='Predicate over a named signal definition. Signals are typed dimensions, similar to feature values: binary signals match true, categorical signals match one of a set of values, and numeric signals match a range. In package signal targeting groups, include/exclude semantics are controlled by the parent group operator, not by negating the expression.', - discriminator='value_type', - title='Signal Targeting Expression', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +SignalTargetingExpression = Annotated[ + SignalTargetingExpression1 | SignalTargetingExpression2 | SignalTargetingExpression3, + Field( + description='Predicate over a named signal definition. Signals are typed dimensions, similar to feature values: binary signals match true, categorical signals match one of a set of values, and numeric signals match a range. In package signal targeting groups, include/exclude semantics are controlled by the parent group operator, not by negating the expression.', + discriminator='value_type', + title='Signal Targeting Expression', + ), +] diff --git a/src/adcp/types/generated_poc/core/start_timing.py b/src/adcp/types/generated_poc/core/start_timing.py index da32add30..3992e2387 100644 --- a/src/adcp/types/generated_poc/core/start_timing.py +++ b/src/adcp/types/generated_poc/core/start_timing.py @@ -1,23 +1,17 @@ # generated by datamodel-codegen: # filename: core/start_timing.json -# timestamp: 2026-06-01T00:32:59+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal -from pydantic import AwareDatetime, Field, RootModel +from pydantic import AwareDatetime, Field -class StartTiming(RootModel[Literal['asap'] | AwareDatetime]): - root: Annotated[ - Literal['asap'] | AwareDatetime, - Field( - description="Campaign start timing: 'asap' or ISO 8601 date-time", title='Start Timing' - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +StartTiming = Annotated[ + Literal['asap'] | AwareDatetime, + Field( + description="Campaign start timing: 'asap' or ISO 8601 date-time", title='Start Timing' + ), +] diff --git a/src/adcp/types/generated_poc/core/targeting.py b/src/adcp/types/generated_poc/core/targeting.py index 491e50e80..bf06f7b28 100644 --- a/src/adcp/types/generated_poc/core/targeting.py +++ b/src/adcp/types/generated_poc/core/targeting.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/targeting.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -212,22 +212,12 @@ class BrowserExclude(RootModel[list[browser_family.BrowserFamily]]): ] -class CollectionListExclude(RootModel[collection_list_ref.CollectionListReference]): - root: Annotated[ - collection_list_ref.CollectionListReference, - Field( - description="Reference to a collection list for excluding specific collections (programs, publications, channels) from this product. Matched collections must not carry the buyer's ads. Use for brand safety do-not-air lists. Seller must declare support in get_adcp_capabilities." - ), - ] +class CollectionListExclude(collection_list_ref.CollectionListReference): + "Reference to a collection list for excluding specific collections (programs, publications, channels) from this product. Matched collections must not carry the buyer's ads. Use for brand safety do-not-air lists. Seller must declare support in get_adcp_capabilities." -class Demographics(RootModel[demographic_targeting_intent.DemographicTargetingIntent]): - root: Annotated[ - demographic_targeting_intent.DemographicTargetingIntent, - Field( - description='Canonical demographic audience targeting intent with optional constraints on how age may be determined. This is distinct from age_restriction: demographics selects an audience, while age_restriction expresses a legal eligibility or verification floor. Fresh create/update targeting MUST compile exactly or be rejected. During get_products, a seller may offer a different configured predicate only through sparse targeting_resolution modifications on a distinguishable product_id; selecting that product accepts the alternative. Sellers never silently broaden, narrow, default, drop, or substitute the basis.' - ), - ] +class Demographics(demographic_targeting_intent.DemographicTargetingIntent): + 'Canonical demographic audience targeting intent with optional constraints on how age may be determined. This is distinct from age_restriction: demographics selects an audience, while age_restriction expresses a legal eligibility or verification floor. Fresh create/update targeting MUST compile exactly or be rejected. During get_products, a seller may offer a different configured predicate only through sparse targeting_resolution modifications on a distinguishable product_id; selecting that product accepts the alternative. Sellers never silently broaden, narrow, default, drop, or substitute the basis.' class DevicePlatform(RootModel[list[device_platform_1.DevicePlatform]]): @@ -320,13 +310,8 @@ class GeoRegionsExclude(RootModel[list[GeoRegionsExcludeItem]]): ] -class PropertyListExclude(RootModel[property_list_ref.PropertyListReference]): - root: Annotated[ - property_list_ref.PropertyListReference, - Field( - description="Reference to a property list whose properties must not carry the buyer's ads. Matched properties are removed from delivery. Use for brand-safety do-not-run lists (apps, sites). Exclude wins on overlap with property_list, and applies regardless of the product's property_targeting_allowed flag. Seller must declare support in get_adcp_capabilities." - ), - ] +class PropertyListExclude(property_list_ref.PropertyListReference): + "Reference to a property list whose properties must not carry the buyer's ads. Matched properties are removed from delivery. Use for brand-safety do-not-run lists (apps, sites). Exclude wins on overlap with property_list, and applies regardless of the product's property_targeting_allowed flag. Seller must declare support in get_adcp_capabilities." class StoreCatchments(RootModel[list[StoreCatchment]]): diff --git a/src/adcp/types/generated_poc/core/targeting_modification.py b/src/adcp/types/generated_poc/core/targeting_modification.py index c9404c784..086fbe0e2 100644 --- a/src/adcp/types/generated_poc/core/targeting_modification.py +++ b/src/adcp/types/generated_poc/core/targeting_modification.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/targeting_modification.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated, Any, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from . import ext as ext_1 @@ -89,17 +89,11 @@ class TargetingModification2(AdCPBaseModel): ext: ext_1.ExtensionObject | None = None -class TargetingModification(RootModel[TargetingModification1 | TargetingModification2]): - root: Annotated[ - TargetingModification1 | TargetingModification2, - Field( - description='Sparse, buyer-reviewable change from targeting requested in get_products to targeting bound to a returned configured product. Entries are applied in array order to the original targeting_overlay; after every entry, the complete result MUST validate against targeting.json. A seller MUST NOT emit two replace operations for the same path or combine replace and remove_values on the same path. Selecting the configured product_id accepts the ordered result.', - discriminator='operation', - title='Targeting Modification', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +TargetingModification = Annotated[ + TargetingModification1 | TargetingModification2, + Field( + description='Sparse, buyer-reviewable change from targeting requested in get_products to targeting bound to a returned configured product. Entries are applied in array order to the original targeting_overlay; after every entry, the complete result MUST validate against targeting.json. A seller MUST NOT emit two replace operations for the same path or combine replace and remove_values on the same path. Selecting the configured product_id accepts the ordered result.', + discriminator='operation', + title='Targeting Modification', + ), +] diff --git a/src/adcp/types/generated_poc/core/targeting_overlay_requirements.py b/src/adcp/types/generated_poc/core/targeting_overlay_requirements.py index 4852bce92..9ae9b0230 100644 --- a/src/adcp/types/generated_poc/core/targeting_overlay_requirements.py +++ b/src/adcp/types/generated_poc/core/targeting_overlay_requirements.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/targeting_overlay_requirements.json -# timestamp: 2026-09-14T14:16:02+00:00 +# timestamp: 2026-10-03T00:35:15+00:00 from __future__ import annotations @@ -39,6 +39,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class KeywordRequirement1(AdCPBaseModel): model_config = ConfigDict( extra='forbid', @@ -54,6 +56,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class BrowserRequirement1(AdCPBaseModel): model_config = ConfigDict( extra='forbid', @@ -69,6 +73,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class DaypartRequirement1(AdCPBaseModel): model_config = ConfigDict( extra='forbid', @@ -91,6 +97,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class GeoProximity(AdCPBaseModel): model_config = ConfigDict( extra='forbid', diff --git a/src/adcp/types/generated_poc/core/targeting_overlay_support.py b/src/adcp/types/generated_poc/core/targeting_overlay_support.py index bca3dcbe3..8fd386fa8 100644 --- a/src/adcp/types/generated_poc/core/targeting_overlay_support.py +++ b/src/adcp/types/generated_poc/core/targeting_overlay_support.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/targeting_overlay_support.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -57,6 +57,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class MetroSupport1(AdCPBaseModel): model_config = ConfigDict( extra='forbid', @@ -81,6 +83,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class SystemVersion(ScalarStr): __slots__ = () _constraints = {'min_length': 1} @@ -145,6 +149,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class BrowserSupport1(AdCPBaseModel): model_config = ConfigDict( extra='forbid', @@ -161,6 +167,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class IanaTimezones(RootModel[list[iana_timezone.IanaTimezoneIdentifier]]): root: Annotated[ list[iana_timezone.IanaTimezoneIdentifier], @@ -193,6 +201,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class GeoProximity(AdCPBaseModel): model_config = ConfigDict( extra='forbid', diff --git a/src/adcp/types/generated_poc/core/tasks_get_request.py b/src/adcp/types/generated_poc/core/tasks_get_request.py index a6c0ac1a6..0108fcb05 100644 --- a/src/adcp/types/generated_poc/core/tasks_get_request.py +++ b/src/adcp/types/generated_poc/core/tasks_get_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/tasks_get_request.json -# timestamp: 2026-08-22T17:50:25+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -20,7 +20,7 @@ class TasksGetRequest(AdcpVersionEnvelope): ) task_id: Annotated[str, Field(description='Unique identifier of the task to retrieve')] account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account scope for the task lookup. Sellers MUST return REFERENCE_NOT_FOUND for a task_id that exists only under a different account or principal. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account.' ), diff --git a/src/adcp/types/generated_poc/core/tasks_list_request.py b/src/adcp/types/generated_poc/core/tasks_list_request.py index 432a768dc..cd4238e5f 100644 --- a/src/adcp/types/generated_poc/core/tasks_list_request.py +++ b/src/adcp/types/generated_poc/core/tasks_list_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/tasks_list_request.json -# timestamp: 2026-06-05T16:54:33+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -96,7 +96,7 @@ class TasksListRequest(AdcpVersionEnvelope): extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description="Account scope for task reconciliation. Sellers MUST only return tasks created for the caller's authenticated account + principal pair. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account." ), diff --git a/src/adcp/types/generated_poc/core/tracker_execution_selector.py b/src/adcp/types/generated_poc/core/tracker_execution_selector.py index c55a8cac7..c3b58ceed 100644 --- a/src/adcp/types/generated_poc/core/tracker_execution_selector.py +++ b/src/adcp/types/generated_poc/core/tracker_execution_selector.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: core/tracker_execution_selector.json -# timestamp: 2026-08-28T20:03:29+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..enums import pixel_tracking_event, tracker_execution_actor, tracker_firing_path from . import daast_tracker_constraints, vast_tracker_constraints @@ -99,19 +99,11 @@ class TrackerExecutionSelector2(VastTrackerConstraints): ] -class TrackerExecutionSelector( - RootModel[TrackerExecutionSelector1 | TrackerExecutionSelector2 | TrackerExecutionSelector3] -): - root: Annotated[ - TrackerExecutionSelector1 | TrackerExecutionSelector2 | TrackerExecutionSelector3, - Field( - description='One exact first-class manifest tracker selector in a seller production execution contract. AdCP 3.2 supports pixel_tracker, vast_tracker, and daast_tracker only; URL-slot selectors are not part of this union.', - discriminator='asset_type', - title='Tracker Execution Selector', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +TrackerExecutionSelector = Annotated[ + TrackerExecutionSelector1 | TrackerExecutionSelector2 | TrackerExecutionSelector3, + Field( + description='One exact first-class manifest tracker selector in a seller production execution contract. AdCP 3.2 supports pixel_tracker, vast_tracker, and daast_tracker only; URL-slot selectors are not part of this union.', + discriminator='asset_type', + title='Tracker Execution Selector', + ), +] diff --git a/src/adcp/types/generated_poc/core/transformer.py b/src/adcp/types/generated_poc/core/transformer.py index 49e0a9fa5..c57d7bf59 100644 --- a/src/adcp/types/generated_poc/core/transformer.py +++ b/src/adcp/types/generated_poc/core/transformer.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/transformer.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, ConfigDict, Field, RootModel, model_validator +from pydantic import AnyUrl, ConfigDict, Field, model_validator from ..enums import channels from ..formats.canonical import ( @@ -1744,49 +1744,25 @@ class InputFormat33(InputFormat16, InputFormat17): pass -class InputFormat( - RootModel[ - InputFormat18 - | InputFormat19 - | InputFormat20 - | InputFormat21 - | InputFormat22 - | InputFormat23 - | InputFormat24 - | InputFormat25 - | InputFormat26 - | InputFormat27 - | InputFormat28 - | InputFormat29 - | InputFormat30 - | InputFormat31 - | InputFormat32 - | InputFormat33 - ] -): - root: ( - InputFormat18 - | InputFormat19 - | InputFormat20 - | InputFormat21 - | InputFormat22 - | InputFormat23 - | InputFormat24 - | InputFormat25 - | InputFormat26 - | InputFormat27 - | InputFormat28 - | InputFormat29 - | InputFormat30 - | InputFormat31 - | InputFormat32 - | InputFormat33 - ) - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +InputFormat = ( + InputFormat18 + | InputFormat19 + | InputFormat20 + | InputFormat21 + | InputFormat22 + | InputFormat23 + | InputFormat24 + | InputFormat25 + | InputFormat26 + | InputFormat27 + | InputFormat28 + | InputFormat29 + | InputFormat30 + | InputFormat31 + | InputFormat32 + | InputFormat33 +) + class Transformer(AdCPBaseModel): model_config = ConfigDict( diff --git a/src/adcp/types/generated_poc/core/vendor_pricing_option.py b/src/adcp/types/generated_poc/core/vendor_pricing_option.py index 646dac4df..239e3b77a 100644 --- a/src/adcp/types/generated_poc/core/vendor_pricing_option.py +++ b/src/adcp/types/generated_poc/core/vendor_pricing_option.py @@ -1,15 +1,15 @@ # generated by datamodel-codegen: # filename: core/vendor_pricing_option.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from . import ext as ext_1 from . import format_id @@ -171,28 +171,14 @@ class VendorPricingOption11(VendorPricingOption5, VendorPricingOption6): pass -class VendorPricingOption( - RootModel[ - VendorPricingOption7 - | VendorPricingOption8 - | VendorPricingOption9 - | VendorPricingOption10 - | VendorPricingOption11 - ] -): - root: Annotated[ - VendorPricingOption7 - | VendorPricingOption8 - | VendorPricingOption9 - | VendorPricingOption10 - | VendorPricingOption11, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +VendorPricingOption = Annotated[ + VendorPricingOption7 + | VendorPricingOption8 + | VendorPricingOption9 + | VendorPricingOption10 + | VendorPricingOption11, + Field( + description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', + title='Vendor Pricing Option', + ), +] diff --git a/src/adcp/types/generated_poc/core/wholesale_feed_event.py b/src/adcp/types/generated_poc/core/wholesale_feed_event.py index c47edbdbf..78d1e43c2 100644 --- a/src/adcp/types/generated_poc/core/wholesale_feed_event.py +++ b/src/adcp/types/generated_poc/core/wholesale_feed_event.py @@ -1,16 +1,16 @@ # generated by datamodel-codegen: # filename: core/wholesale_feed_event.json -# timestamp: 2026-08-28T20:03:29+00:00 +# timestamp: 2026-10-03T00:36:50+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum from collections.abc import Sequence -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from uuid import UUID from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, RootModel +from pydantic import AwareDatetime, ConfigDict, Field from ..enums import signal_catalog_type, signal_value_type from . import canonical_pricing_option @@ -68,20 +68,15 @@ class AppliesTo2(AdCPBaseModel): ] = None -class AppliesTo(RootModel[AppliesTo1 | AppliesTo2]): - root: Annotated[ - AppliesTo1 | AppliesTo2, - Field( - description='Declares which cache layer this event invalidates. See specs/wholesale-feed-webhooks.md §Cache layering and event scoping.', - discriminator='scope', - title='Cache layer scope', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AppliesTo = Annotated[ + AppliesTo1 | AppliesTo2, + Field( + description='Declares which cache layer this event invalidates. See specs/wholesale-feed-webhooks.md §Cache layering and event scoping.', + discriminator='scope', + title='Cache layer scope', + ), +] + class RemovalReason(StrEnum): withdrawn = 'withdrawn' @@ -667,37 +662,19 @@ class WholesaleFeedEvent2(AdCPBaseModel): ] -class WholesaleFeedEvent( - RootModel[ - WholesaleFeedEvent1 - | WholesaleFeedEvent2 - | WholesaleFeedEvent3 - | WholesaleFeedEvent4 - | WholesaleFeedEvent5 - | WholesaleFeedEvent6 - | WholesaleFeedEvent7 - | WholesaleFeedEvent8 - | WholesaleFeedEvent9 - ] -): - root: Annotated[ - WholesaleFeedEvent1 - | WholesaleFeedEvent2 - | WholesaleFeedEvent3 - | WholesaleFeedEvent4 - | WholesaleFeedEvent5 - | WholesaleFeedEvent6 - | WholesaleFeedEvent7 - | WholesaleFeedEvent8 - | WholesaleFeedEvent9, - Field( - description="A single change event emitted by an AdCP agent's wholesale product feed or wholesale signals feed and delivered inside wholesale-feed-webhook payloads. Events are denormalized so consumers can update local state without polling list_products / get_signals. This is distinct from buyer-provided feeds managed by sync_catalogs. The discriminator is event_type; each branch defines the payload shape.", - discriminator='event_type', - title='Wholesale Feed Event', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +WholesaleFeedEvent = Annotated[ + WholesaleFeedEvent1 + | WholesaleFeedEvent2 + | WholesaleFeedEvent3 + | WholesaleFeedEvent4 + | WholesaleFeedEvent5 + | WholesaleFeedEvent6 + | WholesaleFeedEvent7 + | WholesaleFeedEvent8 + | WholesaleFeedEvent9, + Field( + description="A single change event emitted by an AdCP agent's wholesale product feed or wholesale signals feed and delivered inside wholesale-feed-webhook payloads. Events are denormalized so consumers can update local state without polling list_products / get_signals. This is distinct from buyer-provided feeds managed by sync_catalogs. The discriminator is event_type; each branch defines the payload shape.", + discriminator='event_type', + title='Wholesale Feed Event', + ), +] diff --git a/src/adcp/types/generated_poc/creative/get_creative_delivery_request.py b/src/adcp/types/generated_poc/creative/get_creative_delivery_request.py index 305428d57..9f4a4be45 100644 --- a/src/adcp/types/generated_poc/creative/get_creative_delivery_request.py +++ b/src/adcp/types/generated_poc/creative/get_creative_delivery_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: creative/get_creative_delivery_request.json -# timestamp: 2026-05-22T13:15:12+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -20,7 +20,7 @@ class GetCreativeDeliveryRequest(AdcpVersionEnvelope): extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account for routing and scoping. Limits results to creatives within this account.' ), diff --git a/src/adcp/types/generated_poc/creative/get_creative_features_request.py b/src/adcp/types/generated_poc/creative/get_creative_features_request.py index 270d1bd6f..82e813fcd 100644 --- a/src/adcp/types/generated_poc/creative/get_creative_features_request.py +++ b/src/adcp/types/generated_poc/creative/get_creative_features_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: creative/get_creative_features_request.json -# timestamp: 2026-09-24T08:59:48+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -43,7 +43,7 @@ class GetCreativeFeaturesRequest(AdcpVersionEnvelope): ), ] = None account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account for billing this evaluation. Required when the governance agent charges per evaluation.' ), diff --git a/src/adcp/types/generated_poc/creative/list_creative_formats_request.py b/src/adcp/types/generated_poc/creative/list_creative_formats_request.py index fe58e631c..8f10c793d 100644 --- a/src/adcp/types/generated_poc/creative/list_creative_formats_request.py +++ b/src/adcp/types/generated_poc/creative/list_creative_formats_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: creative/list_creative_formats_request.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -129,7 +129,7 @@ class ListCreativeFormatsRequestCreativeAgent(AdcpVersionEnvelope): ), ] = False account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( deprecated=True, description='**DEPRECATED for `list_creative_formats` in 3.1. Removed at 4.0.** Use the account-scoped `list_transformers` request instead. *Legacy 3.x behavior:* identifies the rate card used when `include_pricing` is true.', diff --git a/src/adcp/types/generated_poc/creative/list_creatives_request.py b/src/adcp/types/generated_poc/creative/list_creatives_request.py index 8253b3c2b..e9f878ef3 100644 --- a/src/adcp/types/generated_poc/creative/list_creatives_request.py +++ b/src/adcp/types/generated_poc/creative/list_creatives_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: creative/list_creatives_request.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -126,7 +126,7 @@ class ListCreativesRequest(AdcpVersionEnvelope): ), ] = 50 account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description="Account reference for pricing and access. When provided with include_pricing, the agent returns pricing_options from this account's rate card on each creative." ), diff --git a/src/adcp/types/generated_poc/creative/list_transformers_request.py b/src/adcp/types/generated_poc/creative/list_transformers_request.py index 04eb79cb6..549090775 100644 --- a/src/adcp/types/generated_poc/creative/list_transformers_request.py +++ b/src/adcp/types/generated_poc/creative/list_transformers_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: creative/list_transformers_request.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -102,7 +102,7 @@ class ListTransformersRequestCreativeAgent(AdcpVersionEnvelope): Field(description='Include `pricing_options` on each transformer. Requires `account`.'), ] = False account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account reference. Transformers are account-scoped — the returned set, the enumerable option values, and (with include_pricing) the rate card are all resolved for this credential.' ), diff --git a/src/adcp/types/generated_poc/creative/preview_render.py b/src/adcp/types/generated_poc/creative/preview_render.py index d72a65b96..8926fd45a 100644 --- a/src/adcp/types/generated_poc/creative/preview_render.py +++ b/src/adcp/types/generated_poc/creative/preview_render.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: creative/preview_render.json -# timestamp: 2026-08-19T22:52:24+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel +from pydantic import AnyUrl, ConfigDict, Field from ..core import preview_renderer_metadata @@ -154,17 +154,11 @@ class PreviewRender3(AdCPBaseModel): ] = None -class PreviewRender(RootModel[PreviewRender1 | PreviewRender2 | PreviewRender3]): - root: Annotated[ - PreviewRender1 | PreviewRender2 | PreviewRender3, - Field( - description='A single rendered piece of a creative preview with discriminated output format', - discriminator='output_format', - title='Preview Render', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +PreviewRender = Annotated[ + PreviewRender1 | PreviewRender2 | PreviewRender3, + Field( + description='A single rendered piece of a creative preview with discriminated output format', + discriminator='output_format', + title='Preview Render', + ), +] diff --git a/src/adcp/types/generated_poc/creative/sync_creatives_request.py b/src/adcp/types/generated_poc/creative/sync_creatives_request.py index 0ce5cb35f..cd575acdb 100644 --- a/src/adcp/types/generated_poc/creative/sync_creatives_request.py +++ b/src/adcp/types/generated_poc/creative/sync_creatives_request.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: creative/sync_creatives_request.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..core import account_ref from ..core import context as context_1 @@ -82,18 +82,11 @@ class AssignmentOperations3(AdCPBaseModel): placement_ids: Annotated[list[PlacementId] | None, Field(min_length=1)] = None -class AssignmentOperations( - RootModel[AssignmentOperations1 | AssignmentOperations2 | AssignmentOperations3] -): - root: Annotated[ - AssignmentOperations1 | AssignmentOperations2 | AssignmentOperations3, - Field(discriminator='operation'), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AssignmentOperations = Annotated[ + AssignmentOperations1 | AssignmentOperations2 | AssignmentOperations3, + Field(discriminator='operation'), +] + class Creative(CreativeAsset): revision_id: Annotated[ @@ -115,7 +108,7 @@ class SyncCreativesRequest(AdcpVersionEnvelope): extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2, Field(description='Account that owns these creatives.') + account_ref.AccountReference, Field(description='Account that owns these creatives.') ] creatives: Annotated[ list[Creative] | None, diff --git a/src/adcp/types/generated_poc/creative/validate_input_request.py b/src/adcp/types/generated_poc/creative/validate_input_request.py index 50f622610..aa0df0a23 100644 --- a/src/adcp/types/generated_poc/creative/validate_input_request.py +++ b/src/adcp/types/generated_poc/creative/validate_input_request.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: creative/validate_input_request.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-03T00:36:50+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel +from pydantic import AnyUrl, ConfigDict, Field from ..core import account_ref, brand_ref, creative_manifest @@ -53,20 +53,18 @@ class Targets4(AdCPBaseModel): ] -class Targets(RootModel[Targets1 | Targets2 | Targets3 | Targets4]): - root: Annotated[Targets1 | Targets2 | Targets3 | Targets4, Field(discriminator='kind')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +Targets = Annotated[ + Targets1 | Targets2 | Targets3 | Targets4, + Field(discriminator='kind'), +] + class ValidateInputRequest(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Optional account scope for seller-specific product validation. Required by sellers that route product declarations by buyer account.' ), diff --git a/src/adcp/types/generated_poc/error_details/governance_agent_not_accepted.py b/src/adcp/types/generated_poc/error_details/governance_agent_not_accepted.py index e0e77b7f1..5b6d9c9fd 100644 --- a/src/adcp/types/generated_poc/error_details/governance_agent_not_accepted.py +++ b/src/adcp/types/generated_poc/error_details/governance_agent_not_accepted.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: error_details/governance_agent_not_accepted.json -# timestamp: 2026-08-28T20:03:29+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel +from pydantic import AnyUrl, ConfigDict, Field from ..governance import accepted_governance_agents as accepted_governance_agents_1 @@ -41,19 +41,11 @@ class GovernanceAgentNotAcceptedDetails2(AdCPBaseModel): ] = None -class GovernanceAgentNotAcceptedDetails( - RootModel[GovernanceAgentNotAcceptedDetails1 | GovernanceAgentNotAcceptedDetails2] -): - root: Annotated[ - GovernanceAgentNotAcceptedDetails1 | GovernanceAgentNotAcceptedDetails2, - Field( - description='Per-account rejection details for a governance-agent binding. Sellers choose either the disclosed or opaque arm; callers must not infer undisclosed criteria.', - discriminator='disclosure', - title='Governance Agent Not Accepted Details', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +GovernanceAgentNotAcceptedDetails = Annotated[ + GovernanceAgentNotAcceptedDetails1 | GovernanceAgentNotAcceptedDetails2, + Field( + description='Per-account rejection details for a governance-agent binding. Sellers choose either the disclosed or opaque arm; callers must not infer undisclosed criteria.', + discriminator='disclosure', + title='Governance Agent Not Accepted Details', + ), +] diff --git a/src/adcp/types/generated_poc/error_details/vast_version_mismatch.py b/src/adcp/types/generated_poc/error_details/vast_version_mismatch.py index f6c70b520..293745824 100644 --- a/src/adcp/types/generated_poc/error_details/vast_version_mismatch.py +++ b/src/adcp/types/generated_poc/error_details/vast_version_mismatch.py @@ -1,15 +1,15 @@ # generated by datamodel-codegen: # filename: error_details/vast_version_mismatch.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..core import format_option_ref as format_option_ref_1 from ..enums import vast_version @@ -158,20 +158,10 @@ class VastVersionMismatchDetails3(AdCPBaseModel): ] -class VastVersionMismatchDetails( - RootModel[ - VastVersionMismatchDetails1 | VastVersionMismatchDetails2 | VastVersionMismatchDetails3 - ] -): - root: Annotated[ - VastVersionMismatchDetails1 | VastVersionMismatchDetails2 | VastVersionMismatchDetails3, - Field( - description='Required compatibility details for VAST_VERSION_MISMATCH. Modern emitters discriminate an asset-versus-acceptance mismatch from an inspected-document mismatch. The legacy supported_versions-only arm remains accepted for older 3.x peers.', - title='VAST Version Mismatch Details', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +VastVersionMismatchDetails = Annotated[ + VastVersionMismatchDetails1 | VastVersionMismatchDetails2 | VastVersionMismatchDetails3, + Field( + description='Required compatibility details for VAST_VERSION_MISMATCH. Modern emitters discriminate an asset-versus-acceptance mismatch from an inspected-document mismatch. The legacy supported_versions-only arm remains accepted for older 3.x peers.', + title='VAST Version Mismatch Details', + ), +] diff --git a/src/adcp/types/generated_poc/governance/accepted_governance_agents.py b/src/adcp/types/generated_poc/governance/accepted_governance_agents.py index 480cdcbef..c42bb81fe 100644 --- a/src/adcp/types/generated_poc/governance/accepted_governance_agents.py +++ b/src/adcp/types/generated_poc/governance/accepted_governance_agents.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: governance/accepted_governance_agents.json -# timestamp: 2026-08-28T20:03:29+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -72,6 +72,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class AcceptedGovernanceAgents(AdCPBaseModel): model_config = ConfigDict( extra='forbid', diff --git a/src/adcp/types/generated_poc/governance/check_governance_request.py b/src/adcp/types/generated_poc/governance/check_governance_request.py index 70592ff46..ab5173305 100644 --- a/src/adcp/types/generated_poc/governance/check_governance_request.py +++ b/src/adcp/types/generated_poc/governance/check_governance_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: governance/check_governance_request.json -# timestamp: 2026-09-05T19:08:34+00:00 +# timestamp: 2026-10-02T23:38:16+00:00 from __future__ import annotations @@ -589,4 +589,5 @@ class CheckGovernanceRequest3(CheckGovernanceRequest1, CheckGovernanceRequest2): ext: ext_1.ExtensionObject | None = None -CheckGovernanceRequest = CheckGovernanceRequest3 +class CheckGovernanceRequest(CheckGovernanceRequest3): + 'Universal governance check for campaign actions. The governance agent infers the check type from the fields present: tool+payload = intent check (proposed, orchestrator-side); planned_delivery or delivery_metrics with governance_context = execution or lifecycle check (committed, service-side). Proposal acceptance supplies the immutable proposal separately so governance can inspect its typed commercial terms while payload remains the exact downstream arguments. MediaBuy controls use buyer-proposed and seller-computed positive-delta ceilings. The first check is addressed by plan_id. Subsequent service-side checks use the opaque governance_context as the authoritative plan binding.' diff --git a/src/adcp/types/generated_poc/governance/reported_outcome_error.py b/src/adcp/types/generated_poc/governance/reported_outcome_error.py index 5e18065fd..233377313 100644 --- a/src/adcp/types/generated_poc/governance/reported_outcome_error.py +++ b/src/adcp/types/generated_poc/governance/reported_outcome_error.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: governance/reported_outcome_error.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -36,6 +36,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class BoundedValueLevel31(RootModel[list[BoundedScalar | None]]): root: Annotated[list[BoundedScalar | None], Field(max_length=32)] def __getattr__(self, name: str) -> Any: @@ -44,41 +46,24 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) -class BoundedValueLevel3( - RootModel[BoundedScalar | BoundedValueLevel31 | dict[str, BoundedScalar | None] | None] -): - root: BoundedScalar | BoundedValueLevel31 | dict[str, BoundedScalar | None] | None - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) + + +BoundedValueLevel3 = BoundedScalar | BoundedValueLevel31 | dict[str, BoundedScalar | None] | None + class BoundedValueLevel21(RootModel[list[BoundedValueLevel3]]): root: Annotated[list[BoundedValueLevel3], Field(max_length=32)] -class BoundedValueLevel2( - RootModel[BoundedScalar | BoundedValueLevel21 | dict[str, BoundedValueLevel3] | None] -): - root: BoundedScalar | BoundedValueLevel21 | dict[str, BoundedValueLevel3] | None - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +BoundedValueLevel2 = BoundedScalar | BoundedValueLevel21 | dict[str, BoundedValueLevel3] | None + class BoundedValue1(RootModel[list[BoundedValueLevel2]]): root: Annotated[list[BoundedValueLevel2], Field(max_length=32)] -class BoundedValue(RootModel[BoundedScalar | BoundedValue1 | dict[str, BoundedValueLevel2] | None]): - root: BoundedScalar | BoundedValue1 | dict[str, BoundedValueLevel2] | None - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +BoundedValue = BoundedScalar | BoundedValue1 | dict[str, BoundedValueLevel2] | None + class BoundedObject(RootModel[dict[Annotated[str, StringConstraints(max_length=128)], BoundedValue]]): root: Annotated[dict[Annotated[str, StringConstraints(max_length=128)], BoundedValue], Field(max_length=32)] diff --git a/src/adcp/types/generated_poc/media_buy/accept_proposal_response.py b/src/adcp/types/generated_poc/media_buy/accept_proposal_response.py index f60f35b21..b9cd44783 100644 --- a/src/adcp/types/generated_poc/media_buy/accept_proposal_response.py +++ b/src/adcp/types/generated_poc/media_buy/accept_proposal_response.py @@ -145,4 +145,10 @@ class AcceptProposalResponse5(AcceptProposalResponse1, AcceptProposalResponse4): pass -AcceptProposalResponse = AcceptProposalResponse5 | AcceptProposalResponse6 | AcceptProposalResponse7 +AcceptProposalResponse = Annotated[ + AcceptProposalResponse5 | AcceptProposalResponse6 | AcceptProposalResponse7, + Field( + description='Compact proposal-acceptance result containing the resulting MediaBuy identity and accepted immutable proposal snapshot.', + title='Accept Proposal Response', + ), +] diff --git a/src/adcp/types/generated_poc/media_buy/acceptance_policy_requirement.py b/src/adcp/types/generated_poc/media_buy/acceptance_policy_requirement.py index d9415acc9..978f25770 100644 --- a/src/adcp/types/generated_poc/media_buy/acceptance_policy_requirement.py +++ b/src/adcp/types/generated_poc/media_buy/acceptance_policy_requirement.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: media_buy/acceptance_policy_requirement.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field, RootModel +from pydantic import AwareDatetime, ConfigDict, Field from ..core import ext as ext_1 from ..enums import restricted_attribute @@ -195,53 +195,27 @@ class AcceptancePolicyRequirement17(AdCPBaseModel): ext: ext_1.ExtensionObject | None = None -class AcceptancePolicyRequirement( - RootModel[ - AcceptancePolicyRequirement1 - | AcceptancePolicyRequirement2 - | AcceptancePolicyRequirement3 - | AcceptancePolicyRequirement4 - | AcceptancePolicyRequirement5 - | AcceptancePolicyRequirement6 - | AcceptancePolicyRequirement7 - | AcceptancePolicyRequirement8 - | AcceptancePolicyRequirement9 - | AcceptancePolicyRequirement10 - | AcceptancePolicyRequirement11 - | AcceptancePolicyRequirement12 - | AcceptancePolicyRequirement13 - | AcceptancePolicyRequirement14 - | AcceptancePolicyRequirement15 - | AcceptancePolicyRequirement16 - | AcceptancePolicyRequirement17 - ] -): - root: Annotated[ - AcceptancePolicyRequirement1 - | AcceptancePolicyRequirement2 - | AcceptancePolicyRequirement3 - | AcceptancePolicyRequirement4 - | AcceptancePolicyRequirement5 - | AcceptancePolicyRequirement6 - | AcceptancePolicyRequirement7 - | AcceptancePolicyRequirement8 - | AcceptancePolicyRequirement9 - | AcceptancePolicyRequirement10 - | AcceptancePolicyRequirement11 - | AcceptancePolicyRequirement12 - | AcceptancePolicyRequirement13 - | AcceptancePolicyRequirement14 - | AcceptancePolicyRequirement15 - | AcceptancePolicyRequirement16 - | AcceptancePolicyRequirement17, - Field( - description='A typed prerequisite or restriction attached to a conditional seller-acceptance rule. The kind is coarse and machine-actionable; exact obligations remain in the referenced registry policies.', - discriminator='kind', - title='Acceptance Policy Requirement', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +AcceptancePolicyRequirement = Annotated[ + AcceptancePolicyRequirement1 + | AcceptancePolicyRequirement2 + | AcceptancePolicyRequirement3 + | AcceptancePolicyRequirement4 + | AcceptancePolicyRequirement5 + | AcceptancePolicyRequirement6 + | AcceptancePolicyRequirement7 + | AcceptancePolicyRequirement8 + | AcceptancePolicyRequirement9 + | AcceptancePolicyRequirement10 + | AcceptancePolicyRequirement11 + | AcceptancePolicyRequirement12 + | AcceptancePolicyRequirement13 + | AcceptancePolicyRequirement14 + | AcceptancePolicyRequirement15 + | AcceptancePolicyRequirement16 + | AcceptancePolicyRequirement17, + Field( + description='A typed prerequisite or restriction attached to a conditional seller-acceptance rule. The kind is coarse and machine-actionable; exact obligations remain in the referenced registry policies.', + discriminator='kind', + title='Acceptance Policy Requirement', + ), +] diff --git a/src/adcp/types/generated_poc/media_buy/build_creative_request.py b/src/adcp/types/generated_poc/media_buy/build_creative_request.py index 60637087d..b8ab8046f 100644 --- a/src/adcp/types/generated_poc/media_buy/build_creative_request.py +++ b/src/adcp/types/generated_poc/media_buy/build_creative_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/build_creative_request.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..core import account_ref, brand_ref from ..core import context as context_1 @@ -220,13 +220,8 @@ class SignalCondition7(SignalCondition3, SignalCondition4): pass -class SignalCondition(RootModel[SignalCondition5 | SignalCondition6 | SignalCondition7]): - root: SignalCondition5 | SignalCondition6 | SignalCondition7 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +SignalCondition = SignalCondition5 | SignalCondition6 | SignalCondition7 + class BuildCreativeRequest(AdcpVersionEnvelope): model_config = ConfigDict( @@ -396,7 +391,7 @@ class BuildCreativeRequest(AdcpVersionEnvelope): ), ] = None account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account reference for pricing and billing. When present, the creative agent applies account-specific pricing from the rate card, records the build against the account for billing, and can enforce account-level quotas or entitlements. Required by creative agents that charge for their services.' ), diff --git a/src/adcp/types/generated_poc/media_buy/buy_products_response.py b/src/adcp/types/generated_poc/media_buy/buy_products_response.py index 5e5375940..dd3c43d9b 100644 --- a/src/adcp/types/generated_poc/media_buy/buy_products_response.py +++ b/src/adcp/types/generated_poc/media_buy/buy_products_response.py @@ -145,4 +145,10 @@ class BuyProductsResponse5(BuyProductsResponse1, BuyProductsResponse4): pass -BuyProductsResponse = BuyProductsResponse5 | BuyProductsResponse6 | BuyProductsResponse7 +BuyProductsResponse = Annotated[ + BuyProductsResponse5 | BuyProductsResponse6 | BuyProductsResponse7, + Field( + description='Compact direct-purchase result containing the MediaBuy identity and accepted immutable proposal snapshot.', + title='Buy Products Response', + ), +] diff --git a/src/adcp/types/generated_poc/media_buy/change_term_constraints.py b/src/adcp/types/generated_poc/media_buy/change_term_constraints.py index a2c01c3e7..6e69c476a 100644 --- a/src/adcp/types/generated_poc/media_buy/change_term_constraints.py +++ b/src/adcp/types/generated_poc/media_buy/change_term_constraints.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: media_buy/change_term_constraints.json -# timestamp: 2026-08-28T20:03:29+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, RootModel, model_validator +from pydantic import AwareDatetime, ConfigDict, Field, model_validator from ..core import duration @@ -136,27 +136,14 @@ def _require_portable_bound(self) -> MediaBuyChangeTermConstraints4: return self -class MediaBuyChangeTermConstraints( - RootModel[ - MediaBuyChangeTermConstraints1 - | MediaBuyChangeTermConstraints2 - | MediaBuyChangeTermConstraints3 - | MediaBuyChangeTermConstraints4 - ] -): - root: Annotated[ - MediaBuyChangeTermConstraints1 - | MediaBuyChangeTermConstraints2 - | MediaBuyChangeTermConstraints3 - | MediaBuyChangeTermConstraints4, - Field( - description='Optional machine-readable bounds on a proposal-bound change right. These constraints narrow the action named by the containing change term; they never grant an additional action or expand the accepted commercial envelope. Sellers omit this object when no portable bound is available, and buyers then treat any opaque conditions as unevaluated rather than assuming the change will be accepted.', - discriminator='kind', - title='Media Buy Change Term Constraints', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +MediaBuyChangeTermConstraints = Annotated[ + MediaBuyChangeTermConstraints1 + | MediaBuyChangeTermConstraints2 + | MediaBuyChangeTermConstraints3 + | MediaBuyChangeTermConstraints4, + Field( + description='Optional machine-readable bounds on a proposal-bound change right. These constraints narrow the action named by the containing change term; they never grant an additional action or expand the accepted commercial envelope. Sellers omit this object when no portable bound is available, and buyers then treat any opaque conditions as unevaluated rather than assuming the change will be accepted.', + discriminator='kind', + title='Media Buy Change Term Constraints', + ), +] diff --git a/src/adcp/types/generated_poc/media_buy/commercial_terms.py b/src/adcp/types/generated_poc/media_buy/commercial_terms.py index e89241c94..c97220df5 100644 --- a/src/adcp/types/generated_poc/media_buy/commercial_terms.py +++ b/src/adcp/types/generated_poc/media_buy/commercial_terms.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/commercial_terms.json -# timestamp: 2026-10-01T07:57:26+00:00 +# timestamp: 2026-10-02T23:22:21+00:00 from __future__ import annotations @@ -149,7 +149,7 @@ def _validate_change_term_set(self) -> CommercialTerms: for term in self.change_terms: if term.constraints is None: continue - constraint = term.constraints.root + constraint = term.constraints if constraint.kind == 'budget': money_fields = ( constraint.max_delta_amount, diff --git a/src/adcp/types/generated_poc/media_buy/control_media_buy_response.py b/src/adcp/types/generated_poc/media_buy/control_media_buy_response.py index 25129ed33..31d1898a9 100644 --- a/src/adcp/types/generated_poc/media_buy/control_media_buy_response.py +++ b/src/adcp/types/generated_poc/media_buy/control_media_buy_response.py @@ -87,4 +87,11 @@ class ControlMediaBuyResponse1(AdCPBaseModel): replayed: Literal[True] | None = None -ControlMediaBuyResponse = ControlMediaBuyResponse1 | ControlMediaBuyResponse2 | ControlMediaBuyResponse3 +ControlMediaBuyResponse = Annotated[ + ControlMediaBuyResponse1 | ControlMediaBuyResponse2 | ControlMediaBuyResponse3, + Field( + description='Result of applying operational controls without embedding the package or creative object graphs.', + discriminator='status', + title='Control Media Buy Response', + ), +] diff --git a/src/adcp/types/generated_poc/media_buy/create_media_buy_request.py b/src/adcp/types/generated_poc/media_buy/create_media_buy_request.py index 07db3bcaa..208294d30 100644 --- a/src/adcp/types/generated_poc/media_buy/create_media_buy_request.py +++ b/src/adcp/types/generated_poc/media_buy/create_media_buy_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/create_media_buy_request.json -# timestamp: 2026-10-01T07:57:26+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -178,7 +178,7 @@ class CreateMediaBuyRequest(AdcpVersionEnvelope): ), ] = None account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2, + account_ref.AccountReference, Field( description='Account to bill for this media buy. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts.' ), diff --git a/src/adcp/types/generated_poc/media_buy/decline_proposals_response.py b/src/adcp/types/generated_poc/media_buy/decline_proposals_response.py index 698d06264..002168408 100644 --- a/src/adcp/types/generated_poc/media_buy/decline_proposals_response.py +++ b/src/adcp/types/generated_poc/media_buy/decline_proposals_response.py @@ -5,10 +5,10 @@ from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..core import context as context_1 from ..core import error @@ -51,13 +51,11 @@ class Results2(AdCPBaseModel): ] -class Results(RootModel[Results1 | Results2]): - root: Annotated[Results1 | Results2, Field(discriminator='outcome')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +Results = Annotated[ + Results1 | Results2, + Field(discriminator='outcome'), +] + class Results4(Results1): outcome: Literal['declined'] = 'declined' @@ -67,13 +65,11 @@ class Results5(Results2): outcome: Literal['unable'] = 'unable' -class Results3(RootModel[Results4 | Results5]): - root: Annotated[Results4 | Results5, Field(discriminator='outcome')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +Results3 = Annotated[ + Results4 | Results5, + Field(discriminator='outcome'), +] + class DeclineProposalsResponse1(AdCPBaseModel): model_config = ConfigDict( @@ -103,4 +99,10 @@ class DeclineProposalsResponse2(CompactTaskSubmitted): replayed: Literal[True] | None = None -DeclineProposalsResponse = DeclineProposalsResponse1 | DeclineProposalsResponse2 +DeclineProposalsResponse = Annotated[ + DeclineProposalsResponse1 | DeclineProposalsResponse2, + Field( + description='One ordered terminal result for each requested proposal decline.', + title='Decline Proposals Response', + ), +] diff --git a/src/adcp/types/generated_poc/media_buy/get_media_buy_delivery_request.py b/src/adcp/types/generated_poc/media_buy/get_media_buy_delivery_request.py index d07a28a35..6c6ad47f6 100644 --- a/src/adcp/types/generated_poc/media_buy/get_media_buy_delivery_request.py +++ b/src/adcp/types/generated_poc/media_buy/get_media_buy_delivery_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/get_media_buy_delivery_request.json -# timestamp: 2026-10-01T07:57:26+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -461,7 +461,7 @@ class GetMediaBuyDeliveryRequest(AdcpVersionEnvelope): extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Filter delivery data to a specific account. When omitted, returns data across all accessible accounts.' ), diff --git a/src/adcp/types/generated_poc/media_buy/get_media_buys_request.py b/src/adcp/types/generated_poc/media_buy/get_media_buys_request.py index 4c7669219..5b61d1629 100644 --- a/src/adcp/types/generated_poc/media_buy/get_media_buys_request.py +++ b/src/adcp/types/generated_poc/media_buy/get_media_buys_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/get_media_buys_request.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -31,7 +31,7 @@ class GetMediaBuysRequest(AdcpVersionEnvelope): extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account to retrieve media buys for. When omitted, returns data across all accessible accounts.' ), diff --git a/src/adcp/types/generated_poc/media_buy/get_products_request.py b/src/adcp/types/generated_poc/media_buy/get_products_request.py index f85a5053c..4eda7c8cc 100644 --- a/src/adcp/types/generated_poc/media_buy/get_products_request.py +++ b/src/adcp/types/generated_poc/media_buy/get_products_request.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: media_buy/get_products_request.json -# timestamp: 2026-10-01T07:57:26+00:00 +# timestamp: 2026-10-03T00:36:50+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..core import account_ref, brand_ref from ..core import catalog as catalog_1 @@ -115,13 +115,11 @@ class Refine3(AdCPBaseModel): ] = None -class Refine(RootModel[Refine1 | Refine2 | Refine3]): - root: Annotated[Refine1 | Refine2 | Refine3, Field(discriminator='scope')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +Refine = Annotated[ + Refine1 | Refine2 | Refine3, + Field(discriminator='scope'), +] + class Fields(StrEnum): format_ids = 'format_ids' @@ -252,7 +250,7 @@ class GetProductsRequest(AdcpVersionEnvelope): ), ] = None account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description="Account for product lookup. Returns products with pricing specific to this account's rate card." ), diff --git a/src/adcp/types/generated_poc/media_buy/get_products_response.py b/src/adcp/types/generated_poc/media_buy/get_products_response.py index 969a3f8cc..238ef596d 100644 --- a/src/adcp/types/generated_poc/media_buy/get_products_response.py +++ b/src/adcp/types/generated_poc/media_buy/get_products_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/get_products_response.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -9,7 +9,7 @@ from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel, StringConstraints +from pydantic import ConfigDict, Field, StringConstraints from ..core import context as context_1 from ..core import duration, error @@ -128,15 +128,11 @@ class RefinementApplied3(AdCPBaseModel): ] = None -class RefinementApplied(RootModel[RefinementApplied1 | RefinementApplied2 | RefinementApplied3]): - root: Annotated[ - RefinementApplied1 | RefinementApplied2 | RefinementApplied3, Field(discriminator='scope') - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +RefinementApplied = Annotated[ + RefinementApplied1 | RefinementApplied2 | RefinementApplied3, + Field(discriminator='scope'), +] + class Scope(StrEnum): products = 'products' diff --git a/src/adcp/types/generated_poc/media_buy/legacy_purchase_continuation_input.py b/src/adcp/types/generated_poc/media_buy/legacy_purchase_continuation_input.py index eecad0879..922139c42 100644 --- a/src/adcp/types/generated_poc/media_buy/legacy_purchase_continuation_input.py +++ b/src/adcp/types/generated_poc/media_buy/legacy_purchase_continuation_input.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/legacy_purchase_continuation_input.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -44,7 +44,7 @@ class CompatibilityPurchaseCoordinatorInput(AdCPBaseModel): ), ] account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2, + account_ref.AccountReference, Field( description='Account identity that must match the account bound into the continuation token.' ), diff --git a/src/adcp/types/generated_poc/media_buy/list_creative_formats_request.py b/src/adcp/types/generated_poc/media_buy/list_creative_formats_request.py index 114a80374..03302edde 100644 --- a/src/adcp/types/generated_poc/media_buy/list_creative_formats_request.py +++ b/src/adcp/types/generated_poc/media_buy/list_creative_formats_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/list_creative_formats_request.json -# timestamp: 2026-09-27T20:53:10+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -121,7 +121,7 @@ class ListCreativeFormatsRequest(AdcpVersionEnvelope): ), ] = None account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( deprecated=True, description="**DEPRECATED with `list_creative_formats` in 3.2. Removed at 4.0.** Use `get_products` with `account`; each `Product.format_options[]` lists the formats this seller can deliver for that account. *Legacy 3.x behavior:* scopes the returned formats to this account. Sellers that keep account-specific format catalogs (including sandbox accounts) return that account's formats; sellers with a single catalog MAY ignore this field.", diff --git a/src/adcp/types/generated_poc/media_buy/list_products_response.py b/src/adcp/types/generated_poc/media_buy/list_products_response.py index c304b10d1..9c94c3fe6 100644 --- a/src/adcp/types/generated_poc/media_buy/list_products_response.py +++ b/src/adcp/types/generated_poc/media_buy/list_products_response.py @@ -126,4 +126,11 @@ class ListProductsResponse2(AdCPBaseModel): ext: ext_1.ExtensionObject | None = None -ListProductsResponse = ListProductsResponse1 | ListProductsResponse2 +ListProductsResponse = Annotated[ + ListProductsResponse1 | ListProductsResponse2, + Field( + description="Canonical product offers and continuation state. Every product carries product_id and name; other compact detail fields follow the request's fields selection. Legacy named-format identifiers are never returned. This response never contains proposals or proposal-lifecycle fields.", + discriminator='outcome', + title='List Products Response', + ), +] diff --git a/src/adcp/types/generated_poc/media_buy/media_buy_commitment_response.py b/src/adcp/types/generated_poc/media_buy/media_buy_commitment_response.py index 9f2b0c9b0..28f37b0e1 100644 --- a/src/adcp/types/generated_poc/media_buy/media_buy_commitment_response.py +++ b/src/adcp/types/generated_poc/media_buy/media_buy_commitment_response.py @@ -129,4 +129,11 @@ class MediaBuyCommitmentResponse1(AdCPBaseModel): replayed: Literal[True] | None = None -MediaBuyCommitmentResponse = MediaBuyCommitmentResponse1 | MediaBuyCommitmentResponse2 | MediaBuyCommitmentResponse3 +MediaBuyCommitmentResponse = Annotated[ + MediaBuyCommitmentResponse1 | MediaBuyCommitmentResponse2 | MediaBuyCommitmentResponse3, + Field( + description='Shared result for clean product purchase and proposal acceptance. A successful result returns the MediaBuy identity and the immutable accepted commercial snapshot without embedding creative or package graphs. Optional warnings report non-blocking observations at the commitment boundary; continuing conditions remain readable as indicators through get_media_buys.', + discriminator='status', + title='Media Buy Commitment Response', + ), +] diff --git a/src/adcp/types/generated_poc/media_buy/package_request.py b/src/adcp/types/generated_poc/media_buy/package_request.py index 88d14a908..267d34844 100644 --- a/src/adcp/types/generated_poc/media_buy/package_request.py +++ b/src/adcp/types/generated_poc/media_buy/package_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/package_request.json -# timestamp: 2026-10-01T07:57:26+00:00 +# timestamp: 2026-10-03T00:36:50+00:00 from __future__ import annotations @@ -8,7 +8,7 @@ from typing import Annotated, Any, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, RootModel, model_validator +from pydantic import AwareDatetime, ConfigDict, Field, model_validator from ..core import audience_evidence_pin from ..core import audience_evidence_requirements as audience_evidence_requirements_1 @@ -95,13 +95,11 @@ class CommittedMetrics2(AdCPBaseModel): ] = None -class CommittedMetrics(RootModel[CommittedMetrics1 | CommittedMetrics2]): - root: Annotated[CommittedMetrics1 | CommittedMetrics2, Field(discriminator='scope')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +CommittedMetrics = Annotated[ + CommittedMetrics1 | CommittedMetrics2, + Field(discriminator='scope'), +] + class PackageRequest(AdcpVersionEnvelope): model_config = ConfigDict( diff --git a/src/adcp/types/generated_poc/media_buy/product_purchase.py b/src/adcp/types/generated_poc/media_buy/product_purchase.py index ba2d240ab..7bf5d8f36 100644 --- a/src/adcp/types/generated_poc/media_buy/product_purchase.py +++ b/src/adcp/types/generated_poc/media_buy/product_purchase.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/product_purchase.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -37,8 +37,8 @@ class AgencyEstimateNumber(ScalarStr): } -class Bidding(RootModel[bidding_policy.BiddingPolicy]): - root: bidding_policy.BiddingPolicy +class Bidding(bidding_policy.BiddingPolicy): + pass class Budget(ScalarFloat): @@ -59,13 +59,8 @@ class CatalogIds(RootModel[list[CatalogId]]): ] -class Context(RootModel[context_1.ContextObject]): - root: Annotated[ - context_1.ContextObject, - Field( - description='Opaque buyer package correlation preserved in the accepted snapshot and readback.' - ), - ] +class Context(context_1.ContextObject): + 'Opaque buyer package correlation preserved in the accepted snapshot and readback.' class DailyBudgetCap(ScalarFloat): @@ -85,8 +80,8 @@ class EndTime(RootModel[AwareDatetime]): ] -class Ext(RootModel[ext_1.ExtensionObject]): - root: ext_1.ExtensionObject +class Ext(ext_1.ExtensionObject): + pass class FormatOptionRefs(RootModel[list[format_option_ref.FormatOptionReference]]): @@ -161,13 +156,8 @@ class PerformanceStandards( ] -class Pricing(RootModel[canonical_pricing_option.CanonicalPricingOption]): - root: Annotated[ - canonical_pricing_option.CanonicalPricingOption, - Field( - description='Resolved selected pricing terms. Optional on buy_products input, where pricing_option_id plus the versioned feed identifies the offer; required inside accepted commercial_terms. Its pricing_option_id MUST match the sibling field.' - ), - ] +class Pricing(canonical_pricing_option.CanonicalPricingOption): + 'Resolved selected pricing terms. Optional on buy_products input, where pricing_option_id plus the versioned feed identifies the offer; required inside accepted commercial_terms. Its pricing_option_id MUST match the sibling field.' class ProductPurchase(AdCPBaseModel): diff --git a/src/adcp/types/generated_poc/media_buy/product_refinement.py b/src/adcp/types/generated_poc/media_buy/product_refinement.py index 06806d5e5..d04a34d79 100644 --- a/src/adcp/types/generated_poc/media_buy/product_refinement.py +++ b/src/adcp/types/generated_poc/media_buy/product_refinement.py @@ -1,11 +1,11 @@ # generated by datamodel-codegen: # filename: media_buy/product_refinement.json -# timestamp: 2026-09-01T19:37:00+00:00 +# timestamp: 2026-10-03T00:36:50+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel from pydantic import ConfigDict, Field, RootModel @@ -70,18 +70,11 @@ class ProductRefinementRequests4(AdCPBaseModel): ] = None -class ProductRefinementRequests1( - RootModel[ProductRefinementRequests2 | ProductRefinementRequests3 | ProductRefinementRequests4] -): - root: Annotated[ - ProductRefinementRequests2 | ProductRefinementRequests3 | ProductRefinementRequests4, - Field(discriminator='scope'), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +ProductRefinementRequests1 = Annotated[ + ProductRefinementRequests2 | ProductRefinementRequests3 | ProductRefinementRequests4, + Field(discriminator='scope'), +] + class ProductRefinementRequests(RootModel[list[ProductRefinementRequests1]]): root: Annotated[ diff --git a/src/adcp/types/generated_poc/media_buy/proposal_refinement.py b/src/adcp/types/generated_poc/media_buy/proposal_refinement.py index 3b658d78f..46c391532 100644 --- a/src/adcp/types/generated_poc/media_buy/proposal_refinement.py +++ b/src/adcp/types/generated_poc/media_buy/proposal_refinement.py @@ -1,14 +1,14 @@ # generated by datamodel-codegen: # filename: media_buy/proposal_refinement.json -# timestamp: 2026-09-14T14:16:02+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, RootModel +from pydantic import AwareDatetime, ConfigDict, Field from ..core import product_change_map from . import product_discovery_criteria, proposal_budget_constraint @@ -567,34 +567,17 @@ class ProposalRefinement9(AdCPBaseModel): ] = None -class ProposalRefinement( - RootModel[ - ProposalRefinement2 - | ProposalRefinement3 - | ProposalRefinement4 - | ProposalRefinement5 - | ProposalRefinement6 - | ProposalRefinement7 - | ProposalRefinement8 - | ProposalRefinement9 - ] -): - root: Annotated[ - ProposalRefinement2 - | ProposalRefinement3 - | ProposalRefinement4 - | ProposalRefinement5 - | ProposalRefinement6 - | ProposalRefinement7 - | ProposalRefinement8 - | ProposalRefinement9, - Field( - description='Fork an immutable proposal or finalize a draft into a held committed snapshot. Revising with structured criteria, typed boundaries, product changes, requested alternatives, and/or a semantic ask creates one or more new drafts; finalizing changes no terms. Refining an accepted proposal creates a draft amendment or cancellation proposal against its MediaBuy; the source remains accepted and unchanged. A revised product mix must support the resulting root frequency cap; because omitted criteria inherit, clearing the cap is explicit through remove_media_buy_frequency_cap.', - title='Proposal Refinement', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +ProposalRefinement = Annotated[ + ProposalRefinement2 + | ProposalRefinement3 + | ProposalRefinement4 + | ProposalRefinement5 + | ProposalRefinement6 + | ProposalRefinement7 + | ProposalRefinement8 + | ProposalRefinement9, + Field( + description='Fork an immutable proposal or finalize a draft into a held committed snapshot. Revising with structured criteria, typed boundaries, product changes, requested alternatives, and/or a semantic ask creates one or more new drafts; finalizing changes no terms. Refining an accepted proposal creates a draft amendment or cancellation proposal against its MediaBuy; the source remains accepted and unchanged. A revised product mix must support the resulting root frequency cap; because omitted criteria inherit, clearing the cap is explicit through remove_media_buy_frequency_cap.', + title='Proposal Refinement', + ), +] diff --git a/src/adcp/types/generated_poc/media_buy/refine_proposals_response.py b/src/adcp/types/generated_poc/media_buy/refine_proposals_response.py index d5858a93c..e8aa9c154 100644 --- a/src/adcp/types/generated_poc/media_buy/refine_proposals_response.py +++ b/src/adcp/types/generated_poc/media_buy/refine_proposals_response.py @@ -1,15 +1,15 @@ # generated by datamodel-codegen: # filename: media_buy/refine_proposals_response.json -# timestamp: 2026-10-03T14:21:15+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field, RootModel +from pydantic import AwareDatetime, ConfigDict, Field from ..core import canonical_delivery_forecast, canonical_product, canonical_proposal from ..core import context as context_1 @@ -394,13 +394,11 @@ class Results12(AdCPBaseModel): ] = None -class Results8(RootModel[Results9 | Results10 | Results11 | Results12]): - root: Annotated[Results9 | Results10 | Results11 | Results12, Field(discriminator='outcome')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +Results8 = Annotated[ + Results9 | Results10 | Results11 | Results12, + Field(discriminator='outcome'), +] + class Results15(AdCPBaseModel): model_config = ConfigDict( @@ -541,13 +539,11 @@ class Results18(Results12): outcome: Literal['unable'] = 'unable' -class Results14(RootModel[Results15 | Results16 | Results17 | Results18]): - root: Annotated[Results15 | Results16 | Results17 | Results18, Field(discriminator='outcome')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +Results14 = Annotated[ + Results15 | Results16 | Results17 | Results18, + Field(discriminator='outcome'), +] + class RefineProposalsResponse1(AdCPBaseModel): model_config = ConfigDict( @@ -603,4 +599,10 @@ class RefineProposalsResponse2(CompactTaskSubmitted): replayed: Literal[True] | None = None -RefineProposalsResponse = RefineProposalsResponse1 | RefineProposalsResponse2 +RefineProposalsResponse = Annotated[ + RefineProposalsResponse1 | RefineProposalsResponse2, + Field( + description='One ordered result per requested source proposal. Revision results may carry multiple immutable draft proposals when alternatives were requested; finalization remains one committed proposal per source. Products contains the compact canonical products needed to evaluate the resulting terms.', + title='Refine Proposals Response', + ), +] diff --git a/src/adcp/types/generated_poc/media_buy/request_proposals_response.py b/src/adcp/types/generated_poc/media_buy/request_proposals_response.py index 352b7d204..f7ed48f32 100644 --- a/src/adcp/types/generated_poc/media_buy/request_proposals_response.py +++ b/src/adcp/types/generated_poc/media_buy/request_proposals_response.py @@ -359,9 +359,13 @@ class RequestProposalsResponse4(CompactTaskSubmitted): replayed: Literal[True] | None = None -RequestProposalsResponse = ( +RequestProposalsResponse = Annotated[ RequestProposalsResponse1 | RequestProposalsResponse2 | RequestProposalsResponse3 - | RequestProposalsResponse4 -) + | RequestProposalsResponse4, + Field( + description='One or more immutable draft media-plan proposals and compact canonical products referenced by their purchases. Products always carry product_id and name and never carry legacy named-format identifiers. During the AdCP 3.x compatibility window, an SDK projecting a valid products-only get_products brief result may instead return the deprecated products_available outcome with an explicit purchase continuation. Native 3.2 sellers MUST NOT use that compatibility outcome, and adapters MUST NOT fabricate a proposal, terms digest, or feed version.', + title='Request Proposals Response', + ), +] diff --git a/src/adcp/types/generated_poc/media_buy/sync_audiences_request.py b/src/adcp/types/generated_poc/media_buy/sync_audiences_request.py index 65cf93388..a43d99d9c 100644 --- a/src/adcp/types/generated_poc/media_buy/sync_audiences_request.py +++ b/src/adcp/types/generated_poc/media_buy/sync_audiences_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/sync_audiences_request.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -106,7 +106,7 @@ class SyncAudiencesRequest(AdcpVersionEnvelope): ), ] account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2, Field(description='Account to manage audiences for.') + account_ref.AccountReference, Field(description='Account to manage audiences for.') ] audiences: Annotated[ list[Audience] | None, diff --git a/src/adcp/types/generated_poc/media_buy/sync_catalogs_request.py b/src/adcp/types/generated_poc/media_buy/sync_catalogs_request.py index 2772d8d9d..d230bb12a 100644 --- a/src/adcp/types/generated_poc/media_buy/sync_catalogs_request.py +++ b/src/adcp/types/generated_poc/media_buy/sync_catalogs_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/sync_catalogs_request.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -35,7 +35,7 @@ class SyncCatalogsRequest(AdcpVersionEnvelope): ), ] account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2, + account_ref.AccountReference, Field(description='Seller account containing these buyer-managed catalogs.'), ] catalogs: Annotated[ diff --git a/src/adcp/types/generated_poc/media_buy/sync_event_sources_request.py b/src/adcp/types/generated_poc/media_buy/sync_event_sources_request.py index 1d0457205..27c542416 100644 --- a/src/adcp/types/generated_poc/media_buy/sync_event_sources_request.py +++ b/src/adcp/types/generated_poc/media_buy/sync_event_sources_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/sync_event_sources_request.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -78,7 +78,7 @@ class SyncEventSourcesRequest(AdcpVersionEnvelope): ), ] account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2, Field(description='Account to configure event sources for.') + account_ref.AccountReference, Field(description='Account to configure event sources for.') ] event_sources: Annotated[ list[EventSource] | None, diff --git a/src/adcp/types/generated_poc/media_buy/update_media_buy_request.py b/src/adcp/types/generated_poc/media_buy/update_media_buy_request.py index f45103298..1a8c9f54d 100644 --- a/src/adcp/types/generated_poc/media_buy/update_media_buy_request.py +++ b/src/adcp/types/generated_poc/media_buy/update_media_buy_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: media_buy/update_media_buy_request.json -# timestamp: 2026-10-01T07:57:26+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -46,7 +46,7 @@ class UpdateMediaBuyRequest(AdcpVersionEnvelope): ), ] = None account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2, + account_ref.AccountReference, Field( description='Account that owns this media buy. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. Required for governance checks and account resolution.' ), diff --git a/src/adcp/types/generated_poc/property/base_property_source.py b/src/adcp/types/generated_poc/property/base_property_source.py index b87537548..3b90cc6f3 100644 --- a/src/adcp/types/generated_poc/property/base_property_source.py +++ b/src/adcp/types/generated_poc/property/base_property_source.py @@ -1,13 +1,13 @@ # generated by datamodel-codegen: # filename: property/base_property_source.json -# timestamp: 2026-06-01T00:32:59+00:00 +# timestamp: 2026-10-02T23:18:44+00:00 from __future__ import annotations -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field from ..core import identifier, property_id, property_tag @@ -73,19 +73,11 @@ class BasePropertySource3(AdCPBaseModel): ] -class BasePropertySource( - RootModel[BasePropertySource1 | BasePropertySource2 | BasePropertySource3] -): - root: Annotated[ - BasePropertySource1 | BasePropertySource2 | BasePropertySource3, - Field( - description='A source of properties for a property list. Supports three selection patterns: publisher with tags, publisher with property IDs, or direct identifiers.', - discriminator='selection_type', - title='Base Property Source', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +BasePropertySource = Annotated[ + BasePropertySource1 | BasePropertySource2 | BasePropertySource3, + Field( + description='A source of properties for a property list. Supports three selection patterns: publisher with tags, publisher with property IDs, or direct identifiers.', + discriminator='selection_type', + title='Base Property Source', + ), +] diff --git a/src/adcp/types/generated_poc/property/create_property_list_request.py b/src/adcp/types/generated_poc/property/create_property_list_request.py index 3bc1deb56..dcfedc0e9 100644 --- a/src/adcp/types/generated_poc/property/create_property_list_request.py +++ b/src/adcp/types/generated_poc/property/create_property_list_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: property/create_property_list_request.json -# timestamp: 2026-05-28T10:34:10+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -20,7 +20,7 @@ class CreatePropertyListRequest(AdcpVersionEnvelope): extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account that will own the list. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. When omitted, this task applies its task-local single-account shortcut: if exactly one account is accessible to the authenticated caller, the seller may assign the list to that account; otherwise it MUST return an account-required or ambiguous-account error. Omission MUST NOT mean an undocumented credential-local default account.' ), diff --git a/src/adcp/types/generated_poc/property/delete_property_list_request.py b/src/adcp/types/generated_poc/property/delete_property_list_request.py index cfa19940b..a2db171c3 100644 --- a/src/adcp/types/generated_poc/property/delete_property_list_request.py +++ b/src/adcp/types/generated_poc/property/delete_property_list_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: property/delete_property_list_request.json -# timestamp: 2026-05-22T13:15:12+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -20,7 +20,7 @@ class DeletePropertyListRequest(AdcpVersionEnvelope): ) list_id: Annotated[str, Field(description='ID of the property list to delete')] account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.' ), diff --git a/src/adcp/types/generated_poc/property/get_property_list_request.py b/src/adcp/types/generated_poc/property/get_property_list_request.py index 5ae6eeac5..08dcc0bb3 100644 --- a/src/adcp/types/generated_poc/property/get_property_list_request.py +++ b/src/adcp/types/generated_poc/property/get_property_list_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: property/get_property_list_request.json -# timestamp: 2026-05-22T13:15:12+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -35,7 +35,7 @@ class GetPropertyListRequest(AdcpVersionEnvelope): ) list_id: Annotated[str, Field(description='ID of the property list to retrieve')] account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account that owns the list. Required when the authenticated agent has access to multiple accounts and the list_id is not globally unique within that scope; optional otherwise.' ), diff --git a/src/adcp/types/generated_poc/property/list_property_lists_request.py b/src/adcp/types/generated_poc/property/list_property_lists_request.py index c2bc88ed5..79172da88 100644 --- a/src/adcp/types/generated_poc/property/list_property_lists_request.py +++ b/src/adcp/types/generated_poc/property/list_property_lists_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: property/list_property_lists_request.json -# timestamp: 2026-05-22T13:15:12+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -20,7 +20,7 @@ class ListPropertyListsRequest(AdcpVersionEnvelope): extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Filter to lists owned by this account. When omitted, returns lists across all accounts accessible to the authenticated agent.' ), diff --git a/src/adcp/types/generated_poc/property/property_list.py b/src/adcp/types/generated_poc/property/property_list.py index e9d0b28d1..850cefbe7 100644 --- a/src/adcp/types/generated_poc/property/property_list.py +++ b/src/adcp/types/generated_poc/property/property_list.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: property/property_list.json -# timestamp: 2026-05-02T19:36:29+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -23,7 +23,7 @@ class PropertyList(AdCPBaseModel): None ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account that owns this list. Returned as account_id form (seller-assigned identifier).' ), diff --git a/src/adcp/types/generated_poc/property/update_property_list_request.py b/src/adcp/types/generated_poc/property/update_property_list_request.py index 7bb85ed04..ab995849a 100644 --- a/src/adcp/types/generated_poc/property/update_property_list_request.py +++ b/src/adcp/types/generated_poc/property/update_property_list_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: property/update_property_list_request.json -# timestamp: 2026-05-22T13:15:12+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -21,7 +21,7 @@ class UpdatePropertyListRequest(AdcpVersionEnvelope): ) list_id: Annotated[str, Field(description='ID of the property list to update')] account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.' ), diff --git a/src/adcp/types/generated_poc/property/validate_property_delivery_request.py b/src/adcp/types/generated_poc/property/validate_property_delivery_request.py index c28772b26..f71615deb 100644 --- a/src/adcp/types/generated_poc/property/validate_property_delivery_request.py +++ b/src/adcp/types/generated_poc/property/validate_property_delivery_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: property/validate_property_delivery_request.json -# timestamp: 2026-05-22T13:15:12+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -21,7 +21,7 @@ class ValidatePropertyDeliveryRequest(AdcpVersionEnvelope): ) list_id: Annotated[str, Field(description='ID of the property list to validate against')] account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.' ), diff --git a/src/adcp/types/generated_poc/protocol/get_adcp_capabilities_response.py b/src/adcp/types/generated_poc/protocol/get_adcp_capabilities_response.py index 68caef41d..2d681146e 100644 --- a/src/adcp/types/generated_poc/protocol/get_adcp_capabilities_response.py +++ b/src/adcp/types/generated_poc/protocol/get_adcp_capabilities_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: protocol/get_adcp_capabilities_response.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -313,6 +313,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) return getattr(self.root, name) + + class GovernanceEnforcement(AdCPBaseModel): model_config = ConfigDict( extra='forbid', diff --git a/src/adcp/types/generated_poc/protocol/get_task_status_request.py b/src/adcp/types/generated_poc/protocol/get_task_status_request.py index d2afe676f..2fd9e680a 100644 --- a/src/adcp/types/generated_poc/protocol/get_task_status_request.py +++ b/src/adcp/types/generated_poc/protocol/get_task_status_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: protocol/get_task_status_request.json -# timestamp: 2026-08-22T17:50:25+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -20,7 +20,7 @@ class GetTaskStatusRequest(AdcpVersionEnvelope): ) task_id: Annotated[str, Field(description='Unique identifier of the task to retrieve')] account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account scope for the task lookup. Sellers MUST return REFERENCE_NOT_FOUND for a task_id that exists only under a different account or principal. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account.' ), diff --git a/src/adcp/types/generated_poc/protocol/list_tasks_request.py b/src/adcp/types/generated_poc/protocol/list_tasks_request.py index 1bb75f5f7..65fbde1d2 100644 --- a/src/adcp/types/generated_poc/protocol/list_tasks_request.py +++ b/src/adcp/types/generated_poc/protocol/list_tasks_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: protocol/list_tasks_request.json -# timestamp: 2026-06-05T16:54:33+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -96,7 +96,7 @@ class ListTasksRequest(AdcpVersionEnvelope): extra='allow', ) account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description="Account scope for task reconciliation. Sellers MUST only return tasks created for the caller's authenticated account + principal pair. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account." ), diff --git a/src/adcp/types/generated_poc/signals/activate_signal_request.py b/src/adcp/types/generated_poc/signals/activate_signal_request.py index 075db8543..837279df0 100644 --- a/src/adcp/types/generated_poc/signals/activate_signal_request.py +++ b/src/adcp/types/generated_poc/signals/activate_signal_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: signals/activate_signal_request.json -# timestamp: 2026-08-17T23:02:13+00:00 +# timestamp: 2026-10-02T23:10:32+00:00 from __future__ import annotations @@ -60,7 +60,7 @@ class ActivateSignalRequest(AdcpVersionEnvelope): ), ] = None account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description='Account for this activation. Associates with a commercial relationship established via sync_accounts.' ), diff --git a/src/adcp/types/generated_poc/signals/get_signals_request.py b/src/adcp/types/generated_poc/signals/get_signals_request.py index bad170c40..2419898d9 100644 --- a/src/adcp/types/generated_poc/signals/get_signals_request.py +++ b/src/adcp/types/generated_poc/signals/get_signals_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: signals/get_signals_request.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations @@ -79,7 +79,7 @@ class GetSignalsRequest(AdcpVersionEnvelope): ), ] = DiscoveryMode.brief account: Annotated[ - account_ref.AccountReference1 | account_ref.AccountReference2 | None, + account_ref.AccountReference | None, Field( description="Account for this request. When provided, the signals agent returns per-account pricing options if configured. In 'wholesale' mode, this is the rate-card scope: when omitted in wholesale mode, agents return their default rate-card pricing or omit pricing_options entirely." ), diff --git a/src/adcp/types/generated_poc/trusted_match/provider_registration.py b/src/adcp/types/generated_poc/trusted_match/provider_registration.py index d10c8a403..6e99c81b0 100644 --- a/src/adcp/types/generated_poc/trusted_match/provider_registration.py +++ b/src/adcp/types/generated_poc/trusted_match/provider_registration.py @@ -1,16 +1,16 @@ # generated by datamodel-codegen: # filename: trusted_match/provider_registration.json -# timestamp: 2026-10-02T13:37:43+00:00 +# timestamp: 2026-10-04T00:53:27+00:00 from __future__ import annotations from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal +from typing import Annotated, Literal from uuid import UUID from adcp.types.base import AdCPBaseModel from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, ConfigDict, Field, RootModel, field_validator, model_validator +from pydantic import AnyUrl, ConfigDict, Field, field_validator, model_validator from ..enums import uid_type @@ -228,16 +228,10 @@ def _require_identity_match_dimensions(self) -> TmpProviderRegistration2: return self -class TmpProviderRegistration(RootModel[TmpProviderRegistration1 | TmpProviderRegistration2]): - root: Annotated[ - TmpProviderRegistration1 | TmpProviderRegistration2, - Field( - description="Declares a TMP provider's endpoint, capabilities, and operational parameters. Used in router configuration (static YAML or dynamic API) and referenced by product-level provider entries. The publisher controls which providers participate in their ad decisioning. Endpoint URLs MUST be validated against SSRF, and dynamic registration endpoints MUST authenticate callers — see docs/trusted-match/specification#provider-registration-security.", - title='TMP Provider Registration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) +TmpProviderRegistration = Annotated[ + TmpProviderRegistration1 | TmpProviderRegistration2, + Field( + description="Declares a TMP provider's endpoint, capabilities, and operational parameters. Used in router configuration (static YAML or dynamic API) and referenced by product-level provider entries. The publisher controls which providers participate in their ad decisioning. Endpoint URLs MUST be validated against SSRF, and dynamic registration endpoints MUST authenticate callers — see docs/trusted-match/specification#provider-registration-security.", + title='TMP Provider Registration', + ), +] diff --git a/src/adcp/utils/preview_cache.py b/src/adcp/utils/preview_cache.py index a8890b4d6..31822190e 100644 --- a/src/adcp/utils/preview_cache.py +++ b/src/adcp/utils/preview_cache.py @@ -192,8 +192,7 @@ async def get_preview_data_for_manifest( first_render = preview.renders[0] if preview.renders else None if first_render: - # PreviewRender is a RootModel - access .root for the actual data - render = getattr(first_render, "root", first_render) + render = first_render preview_data = { "preview_id": preview.preview_id, "input": preview.input.model_dump(), diff --git a/src/adcp/utils/response_parser.py b/src/adcp/utils/response_parser.py index f77c7e5c3..fb376f25c 100644 --- a/src/adcp/utils/response_parser.py +++ b/src/adcp/utils/response_parser.py @@ -4,7 +4,7 @@ import json import logging -from typing import Any, TypeVar, Union, cast, get_args, get_origin +from typing import Annotated, Any, TypeVar, Union, cast, get_args, get_origin from pydantic import BaseModel, ValidationError @@ -27,6 +27,11 @@ def _validate_union_type(data: dict[str, Any], response_type: type[T]) -> T: Raises: ValidationError: If data doesn't match any Union variant """ + # A union schema root is published as ``Annotated[A | B, Field(...)]``, so + # peel the metadata before asking whether this is a union. + if get_origin(response_type) is Annotated: + response_type = cast("type[T]", get_args(response_type)[0]) + # Check if this is a Union type (handles both typing.Union and types.UnionType) origin = get_origin(response_type) diff --git a/src/adcp/webhook_sender.py b/src/adcp/webhook_sender.py index f1311dd82..7573dd122 100644 --- a/src/adcp/webhook_sender.py +++ b/src/adcp/webhook_sender.py @@ -38,8 +38,9 @@ import httpx from cryptography.hazmat.primitives.asymmetric import ec, ed25519 -from pydantic import BaseModel +from pydantic import BaseModel, TypeAdapter +from adcp._deferred_adapters import deferred_adapter from adcp.signing.crypto import ( ALG_ED25519, ALG_ES256, @@ -75,6 +76,14 @@ apply_hooks, ) +# WholesaleFeedEvent is a union, so validation goes through an adapter. + + +@deferred_adapter +def _wholesale_feed_event_adapter() -> TypeAdapter[WholesaleFeedEvent]: + return TypeAdapter(WholesaleFeedEvent) + + # NOTE: ``from adcp.webhooks import (...)`` is deliberately at the BOTTOM of this # module (after WebhookSender / WebhookDeliveryResult are defined) to resolve the # webhooks <-> webhook_sender import cycle regardless of which module is imported @@ -1404,9 +1413,9 @@ def prepare_wholesale_feed( if not isinstance(wholesale_feed_version, str) or not wholesale_feed_version: raise ValueError("wholesale_feed_version must be a non-empty string") - event_model = event - if not isinstance(event_model, WholesaleFeedEvent): - event_model = WholesaleFeedEvent.model_validate(event_model) + # WholesaleFeedEvent is a union, so one adapter accepts both an + # already-validated arm and a raw mapping. + event_model = _wholesale_feed_event_adapter().validate_python(event) notification_type_value = _enum_value(notification_type) event_type = _enum_value(event_model.event_type) entity_type = _enum_value(event_model.entity_type) diff --git a/tests/test_account_reference_request_fields.py b/tests/test_account_reference_request_fields.py index d74dde012..f11a43b40 100644 --- a/tests/test_account_reference_request_fields.py +++ b/tests/test_account_reference_request_fields.py @@ -6,6 +6,8 @@ import sys from typing import get_args +from pydantic import RootModel + from adcp.types import ( AccountReferenceById, CompatibilityPurchaseCoordinatorInput, @@ -13,14 +15,19 @@ ListCreativesRequest, SyncAccountsRequest, ) -from adcp.types.generated_poc.core.account_ref import AccountReference as GeneratedAccountReference from adcp.types.versioned import make_versioned_base def _contains_generated_wrapper(annotation: object) -> bool: - return annotation is GeneratedAccountReference or any( - _contains_generated_wrapper(arg) for arg in get_args(annotation) - ) + """Whether a RootModel wrapper appears anywhere in the annotation. + + The obligation is that the field exposes concrete arms. ``AccountReference`` + is itself the union of those arms, so the thing to look for is a wrapper + class, not a name. + """ + if isinstance(annotation, type) and issubclass(annotation, RootModel): + return True + return any(_contains_generated_wrapper(arg) for arg in get_args(annotation)) def test_alias_first_import_order_keeps_concrete_request_arms() -> None: diff --git a/tests/test_adagents.py b/tests/test_adagents.py index d9bdf48c5..4a5c72502 100644 --- a/tests/test_adagents.py +++ b/tests/test_adagents.py @@ -9,6 +9,7 @@ import httpx import pytest +from pydantic import TypeAdapter from adcp.adagents import ( AuthorizationContext, @@ -4037,7 +4038,7 @@ def test_fetch_parser_enforces_reference_renderer_origin(self): def test_generated_publisher_catalog_does_not_invent_registry_role(self): from adcp.types.generated_poc.adagents import AdcpAgentsAuthorization - model = AdcpAgentsAuthorization.model_validate( + model = TypeAdapter(AdcpAgentsAuthorization).validate_python( { "authorized_agents": [ { @@ -4056,7 +4057,7 @@ def test_generated_catalog_validates_nested_reference_renderer(self): from adcp.types.generated_poc.adagents import AdcpAgentsAuthorization with pytest.raises(ValueError, match="Field required"): - AdcpAgentsAuthorization.model_validate( + TypeAdapter(AdcpAgentsAuthorization).validate_python( { "authorized_agents": [], "catalog_etag": "catalog-v1", diff --git a/tests/test_adcp_31_beta4_surface.py b/tests/test_adcp_31_beta4_surface.py index 54b5e9d48..d69284264 100644 --- a/tests/test_adcp_31_beta4_surface.py +++ b/tests/test_adcp_31_beta4_surface.py @@ -2,6 +2,8 @@ from __future__ import annotations +from pydantic import TypeAdapter + _BETA4_EXPORTS = ( "FormatOptionReference", "PackageSignalTargeting", @@ -34,10 +36,10 @@ def test_beta4_symbols_are_publicly_exported() -> None: def test_format_option_references_accept_product_and_publisher_scopes() -> None: from adcp import FormatOptionReference, PackageRequest - product_ref = FormatOptionReference.model_validate( + product_ref = TypeAdapter(FormatOptionReference).validate_python( {"scope": "product", "format_option_id": "display_image"} ) - publisher_ref = FormatOptionReference.model_validate( + publisher_ref = TypeAdapter(FormatOptionReference).validate_python( { "scope": "publisher", "publisher_domain": "example.com", @@ -114,17 +116,17 @@ def test_signal_refs_and_targeting_validate_new_grouped_shapes() -> None: "signals.example.com" ) - binary = SignalTargeting.model_validate( + binary = TypeAdapter(SignalTargeting).validate_python( {"signal_ref": signal_ref, "value_type": "binary", "value": True} ) - categorical = SignalTargeting.model_validate( + categorical = TypeAdapter(SignalTargeting).validate_python( { "signal_ref": signal_ref, "value_type": "categorical", "values": ["suv", "ev"], } ) - numeric = SignalTargeting.model_validate( + numeric = TypeAdapter(SignalTargeting).validate_python( { "signal_ref": signal_ref, "value_type": "numeric", diff --git a/tests/test_canonical_formats_roundtrip.py b/tests/test_canonical_formats_roundtrip.py index 78f07c693..8c7ec5d5b 100644 --- a/tests/test_canonical_formats_roundtrip.py +++ b/tests/test_canonical_formats_roundtrip.py @@ -26,6 +26,7 @@ from typing import Any import pytest +from pydantic import TypeAdapter from adcp.canonical_formats import ( find_declaration_by_v1_format_id, @@ -76,7 +77,7 @@ def _load_product(name: str) -> dict[str, Any]: def _load_declarations(raw_product: dict[str, Any]) -> list[ProductFormatDeclaration]: """Pull the typed ``format_options[]`` out of a raw-dict product.""" return [ - ProductFormatDeclaration.model_validate(opt) + TypeAdapter(ProductFormatDeclaration).validate_python(opt) for opt in raw_product.get("format_options", []) ] diff --git a/tests/test_code_generation.py b/tests/test_code_generation.py index 6f336e358..2ae638a39 100644 --- a/tests/test_code_generation.py +++ b/tests/test_code_generation.py @@ -273,22 +273,25 @@ def test_normalize_enum_descriptions_recurses_into_embedded_schemas(): def test_post_generate_removes_only_unused_pydantic_field_imports(): - from scripts.post_generate_fixes import _remove_unused_pydantic_field_import + from scripts.post_generate_fixes import _remove_unused_imports + + def _remove_unused_pydantic_import(source: str, name: str) -> tuple[str, bool]: + return _remove_unused_imports(source, {"pydantic": (name,)}) unused = ( "from pydantic import ConfigDict, Field\n\n" "class Example:\n" " model_config = ConfigDict(extra='forbid')\n" ) - updated, changed = _remove_unused_pydantic_field_import(unused) + updated, changed = _remove_unused_pydantic_import(unused, "Field") assert changed assert "from pydantic import ConfigDict\n" in updated assert "Field" not in updated - assert _remove_unused_pydantic_field_import(updated) == (updated, False) + assert _remove_unused_pydantic_import(updated, "Field") == (updated, False) used = "from pydantic import Field\n\nvalue = Field(default=None)\n" - assert _remove_unused_pydantic_field_import(used) == (used, False) + assert _remove_unused_pydantic_import(used, "Field") == (used, False) standalone = ( "from __future__ import annotations\n\n" @@ -297,7 +300,7 @@ def test_post_generate_removes_only_unused_pydantic_field_imports(): "class Example(StrEnum):\n" " value = 'value'\n" ) - cleaned, changed = _remove_unused_pydantic_field_import(standalone) + cleaned, changed = _remove_unused_pydantic_import(standalone, "Field") assert changed assert cleaned == ( "from __future__ import annotations\n\n" @@ -823,17 +826,18 @@ def test_allof_merge_preserves_concrete_type_constraints_and_requiredness(tmp_pa def test_generated_adagents_requires_authorization_or_non_empty_catalog(): import pytest - from pydantic import ValidationError + from pydantic import TypeAdapter, ValidationError from adcp.types.generated_poc.adagents import AdcpAgentsAuthorization + adapter = TypeAdapter(AdcpAgentsAuthorization) with pytest.raises(ValidationError): - AdcpAgentsAuthorization.model_validate({"authorized_agents": []}) + adapter.validate_python({"authorized_agents": []}) - model = AdcpAgentsAuthorization.model_validate( + model = adapter.validate_python( {"authorized_agents": [], "formats": [{"format_kind": "image"}]} ) - assert model.root.root.authorized_agents == [] + assert model.authorized_agents == [] def test_post_generate_restores_product_fields_item_reference(tmp_path, monkeypatch): @@ -884,12 +888,14 @@ def test_post_generate_restores_combined_get_products_field_enum(tmp_path, monke @pytest.mark.parametrize("catalog_field", ["properties", "placements", "collections", "signals"]) def test_generated_adagents_rejects_null_required_catalog_arm(catalog_field): - from pydantic import ValidationError + from pydantic import TypeAdapter, ValidationError from adcp.types.generated_poc.adagents import AdcpAgentsAuthorization with pytest.raises(ValidationError): - AdcpAgentsAuthorization.model_validate({"authorized_agents": [], catalog_field: None}) + TypeAdapter(AdcpAgentsAuthorization).validate_python( + {"authorized_agents": [], catalog_field: None} + ) def test_post_generate_injects_postal_pairing_validator_idempotently(tmp_path, monkeypatch): @@ -939,36 +945,248 @@ def test_post_generate_prefers_legacy_postal_union_arm_idempotently(tmp_path, mo assert fixed.count("PostalArea2 | PostalArea1") == 2 -def test_post_generate_exposes_account_reference_union_fields_idempotently(tmp_path, monkeypatch): +def test_unwrap_rootmodel_unions_keeps_field_metadata_and_discriminator(tmp_path, monkeypatch): + """A union root becomes an annotated alias; the Field survives verbatim.""" from scripts import post_generate_fixes generated_dir = tmp_path / "generated_poc" - account_ref = generated_dir / "core" / "account_ref.py" - account_ref.parent.mkdir(parents=True) - account_ref.write_text( - "from pydantic import RootModel\n\n" - "class AccountReference(RootModel[" - "AccountReference1 | AccountReference2 | AccountReference3]):\n" - " pass\n" + generated_dir.mkdir() + target = generated_dir / "deployment.py" + target.write_text( + "from typing import Annotated, Literal\n\n" + "from adcp.types.base import AdCPBaseModel\nfrom pydantic import Field, RootModel\n\n\n" + "class Deployment1(AdCPBaseModel):\n" + " type: Literal['platform'] = 'platform'\n\n\n" + "class Deployment2(AdCPBaseModel):\n" + " type: Literal['agent'] = 'agent'\n\n\n" + "class Deployment(RootModel[Deployment1 | Deployment2]):\n" + " root: Annotated[\n" + " Deployment1 | Deployment2,\n" + " Field(\n" + " description='A signal deployment',\n" + " discriminator='type',\n" + " title='Deployment',\n" + " ),\n" + " ]\n" + ) + monkeypatch.setattr(post_generate_fixes, "OUTPUT_DIR", generated_dir) + + post_generate_fixes.unwrap_rootmodel_unions() + + assert target.read_text().endswith( + "Deployment = Annotated[\n" + " Deployment1 | Deployment2,\n" + " Field(\n" + " description='A signal deployment',\n" + " discriminator='type',\n" + " title='Deployment',\n" + " ),\n" + "]\n" + ) + + +def test_unwrap_rootmodel_unions_keeps_value_roots_wrapped(tmp_path, monkeypatch): + """A value root carries its constraints on the wrapper, so it stays one.""" + from scripts import post_generate_fixes + + generated_dir = tmp_path / "generated_poc" + generated_dir.mkdir() + target = generated_dir / "values.py" + source = ( + "from typing import Annotated, Literal\n\n" + "from pydantic import Field, RootModel\n\n\n" + "class PropertyId(RootModel[str]):\n" + " root: Annotated[str, Field(min_length=1)]\n\n\n" + "class ProfileIds(RootModel[list[str]]):\n" + " root: Annotated[list[str], Field(min_length=1)]\n\n\n" + "class CapScope(RootModel[Literal['package']]):\n" + " root: Literal['package']\n" + ) + target.write_text(source) + monkeypatch.setattr(post_generate_fixes, "OUTPUT_DIR", generated_dir) + + post_generate_fixes.unwrap_rootmodel_unions() + + assert target.read_text() == source + + +def test_unwrap_rootmodel_unions_keeps_arms_of_a_discriminated_union(tmp_path, monkeypatch): + """A wrapper the parent's discriminator maps a tag to keeps its wrapper. + + Both ``VastAsset`` shapes carry ``asset_type='vast'``, so inlining the union + would give the parent's ``asset_type`` two choices for one tag. + """ + from scripts import post_generate_fixes + + generated_dir = tmp_path / "generated_poc" + generated_dir.mkdir() + target = generated_dir / "asset_union.py" + target.write_text( + "from typing import Annotated, Literal\n\n" + "from adcp.types.base import AdCPBaseModel\nfrom pydantic import Field, RootModel\n\n\n" + "class VastAsset1(AdCPBaseModel):\n" + " asset_type: Literal['vast'] = 'vast'\n" + " delivery_type: Literal['url'] = 'url'\n\n\n" + "class VastAsset2(AdCPBaseModel):\n" + " asset_type: Literal['vast'] = 'vast'\n" + " delivery_type: Literal['inline'] = 'inline'\n\n\n" + "class ImageAsset(AdCPBaseModel):\n" + " asset_type: Literal['image'] = 'image'\n\n\n" + "class VastAsset(RootModel[VastAsset1 | VastAsset2]):\n" + " root: Annotated[VastAsset1 | VastAsset2, Field(discriminator='delivery_type')]\n\n\n" + "class AssetVariant(RootModel[ImageAsset | VastAsset]):\n" + " root: Annotated[ImageAsset | VastAsset, Field(discriminator='asset_type')]\n" + ) + monkeypatch.setattr(post_generate_fixes, "OUTPUT_DIR", generated_dir) + + post_generate_fixes.unwrap_rootmodel_unions() + + rewritten = target.read_text() + assert "class VastAsset(RootModel[VastAsset1 | VastAsset2]):" in rewritten + assert "AssetVariant = Annotated[" in rewritten + + +def test_unwrap_rootmodel_unions_keeps_an_arm_of_another_modules_field_union(tmp_path, monkeypatch): + """A composing root another module's smart union names as an arm keeps its wrapper. + + ``creative_localization.assets`` is ``LocalizedCreativeAsset | Assets``; inlining + the root would let Pydantic's smart mode score its arms against ``Assets`` + directly and pick a different one for the same payload. A discriminated root + is exempt (a nested tagged union is selected by its tag), and so is a root a + union *root* composes — that flattening predates this pass. + """ + from scripts import post_generate_fixes + + generated_dir = tmp_path / "generated_poc" + (generated_dir / "core").mkdir(parents=True) + (generated_dir / "core" / "localized_creative_asset.py").write_text( + "from typing import Annotated\n\n" + "from adcp.types.base import AdCPBaseModel\nfrom pydantic import Field, RootModel\n\n\n" + "class LocalizedCreativeAsset1(AdCPBaseModel):\n url: str\n\n\n" + "class LocalizedCreativeAsset2(AdCPBaseModel):\n text: str\n\n\n" + "class LocalizedCreativeAsset(RootModel[LocalizedCreativeAsset1 | LocalizedCreativeAsset2]):\n" + " root: Annotated[LocalizedCreativeAsset1 | LocalizedCreativeAsset2, Field(title='x')]\n" + ) + (generated_dir / "core" / "tagged.py").write_text( + "from typing import Annotated, Literal\n\n" + "from adcp.types.base import AdCPBaseModel\nfrom pydantic import Field, RootModel\n\n\n" + "class Tagged1(AdCPBaseModel):\n kind: Literal['a'] = 'a'\n\n\n" + "class Tagged2(AdCPBaseModel):\n kind: Literal['b'] = 'b'\n\n\n" + "class Tagged(RootModel[Tagged1 | Tagged2]):\n" + " root: Annotated[Tagged1 | Tagged2, Field(discriminator='kind')]\n" + ) + (generated_dir / "core" / "creative_localization.py").write_text( + "from adcp.types.base import AdCPBaseModel\n\n" + "from . import localized_creative_asset, tagged\n\n\n" + "class Assets(AdCPBaseModel):\n pass\n\n\n" + "class CreativeLocalization(AdCPBaseModel):\n" + " assets: localized_creative_asset.LocalizedCreativeAsset | Assets | None = None\n" + " choice: tagged.Tagged | Assets | None = None\n" + ) + (generated_dir / "core" / "composed_root.py").write_text( + "from typing import Annotated\n\n" + "from adcp.types.base import AdCPBaseModel\nfrom pydantic import Field, RootModel\n\n" + "from . import localized_creative_asset\n\n\n" + "class Other(AdCPBaseModel):\n pass\n\n\n" + "class ComposedRoot(RootModel[Other | localized_creative_asset.LocalizedCreativeAsset]):\n" + " root: Annotated[Other | localized_creative_asset.LocalizedCreativeAsset, Field(title='r')]\n" ) - target = generated_dir / "sample_request.py" + monkeypatch.setattr(post_generate_fixes, "OUTPUT_DIR", generated_dir) + + post_generate_fixes.unwrap_rootmodel_unions() + + kept = (generated_dir / "core" / "localized_creative_asset.py").read_text() + assert "class LocalizedCreativeAsset(RootModel[" in kept + assert "Tagged = Annotated[" in (generated_dir / "core" / "tagged.py").read_text() + # The union root that composes the kept root is itself unwrapped as before. + assert "ComposedRoot = Annotated[" in (generated_dir / "core" / "composed_root.py").read_text() + + +def test_unwrap_rootmodel_unions_subclasses_a_single_model_root(tmp_path, monkeypatch): + """A root naming one model becomes a subclass carrying its description.""" + from scripts import post_generate_fixes + + generated_dir = tmp_path / "generated_poc" + generated_dir.mkdir() + target = generated_dir / "check_governance_request.py" target.write_text( - "account: account_ref.AccountReference | None\n" - "accounts: list[account_ref_1.AccountReference]\n" + "from typing import Annotated\n\n" + "from adcp.types.base import AdCPBaseModel\nfrom pydantic import Field, RootModel\n\n\n" + "class CheckGovernanceRequest3(AdCPBaseModel):\n" + " plan_id: str\n\n\n" + "class CheckGovernanceRequest(RootModel[CheckGovernanceRequest3]):\n" + " root: Annotated[\n" + " CheckGovernanceRequest3,\n" + " Field(description='Universal governance check.'),\n" + " ]\n" ) monkeypatch.setattr(post_generate_fixes, "OUTPUT_DIR", generated_dir) - post_generate_fixes.expose_account_reference_union_fields() - post_generate_fixes.expose_account_reference_union_fields() + post_generate_fixes.unwrap_rootmodel_unions() - assert target.read_text() == ( - "account: account_ref.AccountReference1 | account_ref.AccountReference2 | " - "account_ref.AccountReference3 | None\n" - "accounts: list[account_ref_1.AccountReference1 | account_ref_1.AccountReference2 | " - "account_ref_1.AccountReference3]\n" + assert target.read_text().endswith( + "class CheckGovernanceRequest(CheckGovernanceRequest3):\n" + " 'Universal governance check.'\n" ) +def test_unwrap_rootmodel_unions_keeps_a_root_with_a_validator(tmp_path, monkeypatch): + """The wrapper is the only place a cross-arm validator can live.""" + from scripts import post_generate_fixes + + generated_dir = tmp_path / "generated_poc" + generated_dir.mkdir() + target = generated_dir / "postal_area.py" + source = ( + "from typing import Annotated\n\n" + "from pydantic import Field, RootModel, model_validator\n\n\n" + "class PostalArea(RootModel[PostalArea1 | PostalArea2]):\n" + " root: Annotated[PostalArea1 | PostalArea2, Field(title='Postal Area')]\n\n" + " @model_validator(mode='after')\n" + " def _check(self) -> 'PostalArea':\n" + " return self\n" + ) + target.write_text(source) + monkeypatch.setattr(post_generate_fixes, "OUTPUT_DIR", generated_dir) + + post_generate_fixes.unwrap_rootmodel_unions() + + assert target.read_text() == source + + +def test_discriminated_union_roots_report_one_tag_error(): + """A union the schema discriminates reports the tag, not every arm. + + ``core/deployment.json`` and ``media-buy/list-products-response.json`` both + declare ``discriminator``. An unwrap that drops it turns one + ``union_tag_invalid`` into one error per arm per mismatched field. + """ + import pytest + from pydantic import TypeAdapter, ValidationError + + from adcp.types import ListProductsResponse + from adcp.types.generated_poc.core.deployment import Deployment + + for union_type, payload in ( + (Deployment, {"type": "nope", "is_live": True}), + (ListProductsResponse, {"outcome": "nope", "feed_version": "v1"}), + ): + with pytest.raises(ValidationError) as caught: + TypeAdapter(union_type).validate_python(payload) + errors = caught.value.errors() + assert [error["type"] for error in errors] == ["union_tag_invalid"], errors + + +def test_account_reference_arms_are_reachable_through_the_alias(): + """The union alias exposes its arms wherever a field references it.""" + import types as builtin_types + + from adcp.types import AccountReference, AccountReferenceById + + assert isinstance(AccountReference, builtin_types.UnionType) + assert AccountReferenceById in AccountReference.__args__ + + def test_product_change_map_uses_valid_constrained_string_key_type(): """Constrained mapping keys must be valid for Pydantic and static type checkers.""" from adcp.types.generated_poc.core.product_change_map import ProductChangeMap @@ -1673,8 +1891,10 @@ def test_no_request_response_rootmodels(): or custom fields. All Request/Response union types should be unwrapped to plain Union type aliases in post_generate_fixes.py. - If this test fails after a schema update, add the new type to - _UNWRAP_TO_UNION in scripts/post_generate_fixes.py. + unwrap_rootmodel_unions() in post_generate_fixes.py derives this: a root + that composes is emitted as what it composes. A type reaching this guard has + a root the wrapper still carries — a value, a validator, or a tag another + union discriminates on — and the schema is what to look at. See: https://github.com/adcontextprotocol/adcp-client-python/issues/155 """ @@ -1705,8 +1925,7 @@ def test_no_request_response_rootmodels(): assert rootmodel_violations == [], ( f"These Request/Response types are RootModel classes, which blocks " - f"consumer subclassing. Add them to _UNWRAP_TO_UNION in " - f"scripts/post_generate_fixes.py: {rootmodel_violations}" + f"consumer subclassing: {rootmodel_violations}" ) diff --git a/tests/test_codegen_contract_compatibility.py b/tests/test_codegen_contract_compatibility.py index 2dcd471bf..d4794bf74 100644 --- a/tests/test_codegen_contract_compatibility.py +++ b/tests/test_codegen_contract_compatibility.py @@ -44,10 +44,16 @@ def test_response_dispatch_omits_newer_pydantic_keywords_at_their_defaults() -> def test_product_signal_targeting_option_keeps_discriminated_signal_ref() -> None: + from typing import get_args + from adcp import ProductSignalTargetingOption from adcp.types.generated_poc.core.signal_ref import SignalRef - assert ProductSignalTargetingOption.model_fields["signal_ref"].annotation is SignalRef + # SignalRef is Annotated[SignalRef1 | SignalRef2 | SignalRef3, Field(...)]; + # Pydantic lifts the metadata into FieldInfo and keeps the union. + arms, metadata = get_args(SignalRef) + assert ProductSignalTargetingOption.model_fields["signal_ref"].annotation is arms + assert metadata.discriminator == "scope" assert ProductSignalTargetingOption.model_json_schema()["properties"]["signal_ref"][ "description" ].startswith("Canonical signal reference.") @@ -55,15 +61,17 @@ def test_product_signal_targeting_option_keeps_discriminated_signal_ref() -> Non option = ProductSignalTargetingOption.model_validate( {"signal_ref": {"scope": "product", "signal_id": "signal_1"}} ) - assert isinstance(option.signal_ref, SignalRef) + assert isinstance(option.signal_ref, get_args(arms)) assert option.signal_ref.scope == "product" - for invalid_signal_ref in ( - {"scope": "unknown", "signal_id": "signal_1"}, - "signal_1", - ): - with pytest.raises(ValidationError): - ProductSignalTargetingOption.model_validate({"signal_ref": invalid_signal_ref}) + with pytest.raises(ValidationError) as unknown_scope: + ProductSignalTargetingOption.model_validate( + {"signal_ref": {"scope": "unknown", "signal_id": "signal_1"}} + ) + assert [error["type"] for error in unknown_scope.value.errors()] == ["union_tag_invalid"] + + with pytest.raises(ValidationError): + ProductSignalTargetingOption.model_validate({"signal_ref": "signal_1"}) def test_creative_representation_keeps_canonical_format_contract() -> None: diff --git a/tests/test_decisioning_refine.py b/tests/test_decisioning_refine.py index 7921c64f9..46bfece43 100644 --- a/tests/test_decisioning_refine.py +++ b/tests/test_decisioning_refine.py @@ -143,7 +143,7 @@ def test_refinement_applied_request_scope_echoes_scope() -> None: outcomes = [RefinementOutcome(status="applied", notes="added video products")] out = build_refinement_applied(req.refine or [], outcomes) assert len(out) == 1 - inner = out[0].root + inner = out[0] assert inner.scope == "request" assert inner.status.value == "applied" assert inner.notes == "added video products" @@ -153,7 +153,7 @@ def test_refinement_applied_product_scope_echoes_product_id() -> None: req = _refine_request_product_scope("prod_42") outcomes = [RefinementOutcome(status="partial", notes="updated pricing only")] out = build_refinement_applied(req.refine or [], outcomes) - inner = out[0].root + inner = out[0] assert inner.scope == "product" assert inner.product_id == "prod_42" assert inner.status.value == "partial" @@ -163,7 +163,7 @@ def test_refinement_applied_proposal_scope_echoes_proposal_id() -> None: req = _refine_request_proposal_scope("prop_xyz") outcomes = [RefinementOutcome(status="unable", notes="not available in inventory")] out = build_refinement_applied(req.refine or [], outcomes) - inner = out[0].root + inner = out[0] assert inner.scope == "proposal" assert inner.proposal_id == "prop_xyz" assert inner.status.value == "unable" @@ -186,11 +186,11 @@ def test_refinement_applied_mixed_outcomes() -> None: RefinementOutcome(status="partial", notes="capacity reduced"), ] out = build_refinement_applied(req.refine or [], outcomes) - assert [r.root.scope for r in out] == ["product", "product", "proposal"] - assert out[0].root.product_id == "p1" - assert out[1].root.product_id == "p2" - assert out[2].root.proposal_id == "pp1" - assert out[2].root.notes == "capacity reduced" + assert [r.scope for r in out] == ["product", "product", "proposal"] + assert out[0].product_id == "p1" + assert out[1].product_id == "p2" + assert out[2].proposal_id == "pp1" + assert out[2].notes == "capacity reduced" def test_mismatched_outcome_count_raises() -> None: @@ -216,7 +216,7 @@ def test_project_refine_response_attaches_products_and_outcomes() -> None: assert response.proposals is None assert response.refinement_applied is not None assert len(response.refinement_applied) == 1 - assert response.refinement_applied[0].root.scope == "request" + assert response.refinement_applied[0].scope == "request" def test_project_refine_response_keeps_proposals_when_provided() -> None: @@ -286,8 +286,8 @@ async def test_handler_dispatches_to_refine_get_products(executor) -> None: class _P(_PlatformWithRefine): def refine_get_products(self, req, ctx): - called["scope"] = req.refine[0].root.scope - called["ask"] = req.refine[0].root.ask + called["scope"] = req.refine[0].scope + called["ask"] = req.refine[0].ask return RefineResult( products=[], proposals=None, @@ -299,7 +299,7 @@ def refine_get_products(self, req, ctx): resp = await handler.get_products(req, ToolContext()) assert called == {"scope": "request", "ask": "less display"} assert resp.refinement_applied is not None - assert resp.refinement_applied[0].root.notes == "ok" + assert resp.refinement_applied[0].notes == "ok" @pytest.mark.asyncio diff --git a/tests/test_generated_hierarchy.py b/tests/test_generated_hierarchy.py index ed4e22708..819a726d7 100644 --- a/tests/test_generated_hierarchy.py +++ b/tests/test_generated_hierarchy.py @@ -242,6 +242,10 @@ def _resolve(rel: Path, names: tuple[str, ...]) -> tuple[type, ...]: if isinstance(obj, type): found[obj] = None continue + # A composing root is ``Annotated[A | B, Field(...)]``: the metadata + # carries the schema's discriminator, and the union is its first arg. + if typing.get_origin(obj) is typing.Annotated: + obj = typing.get_args(obj)[0] arms = typing.get_args(obj) assert arms, f"{_module_name(rel)}.{name} is neither a class nor a union: {obj!r}" for arm in arms: diff --git a/tests/test_manifest_response_readback.py b/tests/test_manifest_response_readback.py index a74f6989c..8e52a636e 100644 --- a/tests/test_manifest_response_readback.py +++ b/tests/test_manifest_response_readback.py @@ -171,19 +171,20 @@ def completed_task(case): @pytest.mark.parametrize("case", TASK_CASES) def test_async_response_union_and_webhook_round_trip(case): model, payload = response_case(case) - result = AdcpAsyncResponseData.model_validate_json(json.dumps(payload)) - assert isinstance(result.root, model) - assert read_manifest(result.root, case).format_kind == FUTURE_KIND - reparsed = AdcpAsyncResponseData.model_validate_json(result.model_dump_json()) - assert isinstance(reparsed.root, model) - assert read_manifest(reparsed.root, case).format_kind == FUTURE_KIND + adapter = TypeAdapter(AdcpAsyncResponseData) + result = adapter.validate_json(json.dumps(payload)) + assert isinstance(result, model) + assert read_manifest(result, case).format_kind == FUTURE_KIND + reparsed = adapter.validate_json(adapter.dump_json(result)) + assert isinstance(reparsed, model) + assert read_manifest(reparsed, case).format_kind == FUTURE_KIND webhook = McpWebhookPayload.model_validate_json(json.dumps(completed_task(case))) - assert isinstance(webhook.result.root, model) - assert read_manifest(webhook.result.root, case).format_kind == FUTURE_KIND + assert isinstance(webhook.result, model) + assert read_manifest(webhook.result, case).format_kind == FUTURE_KIND reparsed_webhook = McpWebhookPayload.model_validate_json(webhook.model_dump_json()) - assert isinstance(reparsed_webhook.result.root, model) - assert read_manifest(reparsed_webhook.result.root, case).format_kind == FUTURE_KIND + assert isinstance(reparsed_webhook.result, model) + assert read_manifest(reparsed_webhook.result, case).format_kind == FUTURE_KIND @pytest.mark.asyncio @@ -206,11 +207,11 @@ async def test_public_completed_task_parser_keeps_manifest_readback(case): assert result.success, result.error assert result.status is TaskStatus.COMPLETED # A successful untyped fallback would silently hide strict-union rejection. - assert isinstance(result.data.root, model) - assert read_manifest(result.data.root, case).format_kind == FUTURE_KIND + assert isinstance(result.data, model) + assert read_manifest(result.data, case).format_kind == FUTURE_KIND reparsed = TaskResult[AdcpAsyncResponseData].model_validate_json(result.model_dump_json()) - assert isinstance(reparsed.data.root, model) - assert read_manifest(reparsed.data.root, case).format_kind == FUTURE_KIND + assert isinstance(reparsed.data, model) + assert read_manifest(reparsed.data, case).format_kind == FUTURE_KIND @pytest.mark.parametrize("case", CASES) diff --git a/tests/test_media_buy_actions.py b/tests/test_media_buy_actions.py index eaf3d0c15..c826e3439 100644 --- a/tests/test_media_buy_actions.py +++ b/tests/test_media_buy_actions.py @@ -6,7 +6,7 @@ from typing import Any import pytest -from pydantic import BaseModel, ValidationError +from pydantic import BaseModel, TypeAdapter, ValidationError from adcp.media_buy_actions import ( ActionAvailabilityStatus, @@ -461,7 +461,9 @@ def test_generated_canonical_action_requires_action_field() -> None: from adcp.types import CanonicalMediaBuyAction with pytest.raises(ValidationError): - CanonicalMediaBuyAction.model_validate({"task": "control_media_buy", "mode": "self_serve"}) + TypeAdapter(CanonicalMediaBuyAction).validate_python( + {"task": "control_media_buy", "mode": "self_serve"} + ) def test_generated_change_term_rejects_incompatible_constraint_kind() -> None: diff --git a/tests/test_preview_html.py b/tests/test_preview_html.py index c3721abdc..165a1a956 100644 --- a/tests/test_preview_html.py +++ b/tests/test_preview_html.py @@ -250,8 +250,7 @@ async def test_preview_creative(): assert result.success assert result.data assert len(result.data.previews) == 1 - # PreviewRender is a RootModel - access .root for the actual variant data - render = result.data.previews[0].renders[0].root + render = result.data.previews[0].renders[0] assert str(render.preview_url) == "https://preview.example.com/abc123" mock_call.assert_called_once() diff --git a/tests/test_reporting_adjustment_evidence.py b/tests/test_reporting_adjustment_evidence.py index 81befbf9c..7bcbc360d 100644 --- a/tests/test_reporting_adjustment_evidence.py +++ b/tests/test_reporting_adjustment_evidence.py @@ -140,8 +140,8 @@ def test_mapping_is_snapshot_with_explicit_upstream_information_loss_limit() -> evidence = capture_reporting_adjustment_evidence(raw, typed_adjustment=typed, scope=SCOPE) before = evidence.raw_json, evidence.canonical_json, evidence.observed_adjustment_sha256 raw["control_total_deltas"][0]["value"] = "999" - typed.control_total_deltas[0].root.value = "999" - evidence.adjustment.control_total_deltas[0].root.value = "999" + typed.control_total_deltas[0].value = "999" + evidence.adjustment.control_total_deltas[0].value = "999" assert ( evidence.raw_json, evidence.canonical_json, @@ -542,7 +542,7 @@ def test_selected_context_requires_exact_ownership_and_reconciled_billing_offici SCOPE, obligation=obligation, revision=revision, revision_owner="obligation-1" ) assert context == CONTEXT - revision.control_totals[0].root.unit = "EUR" + revision.control_totals[0].unit = "EUR" assert context.control_total_units == (("spend", "USD"),) assert build(context=context).status == "accepted" rejected( diff --git a/tests/test_reporting_reconciliation.py b/tests/test_reporting_reconciliation.py index 6cf1269e3..ba2cf47f7 100644 --- a/tests/test_reporting_reconciliation.py +++ b/tests/test_reporting_reconciliation.py @@ -11,6 +11,7 @@ import httpx import pytest import rfc8785 +from pydantic import TypeAdapter from adcp.decisioning.capabilities import MediaBuy from adcp.reporting import ( @@ -894,7 +895,9 @@ async def inspect(_: ReportingInspectionContext) -> ReportingObservation: raise OSError("transient warehouse read") return ReportingObservation( row_count=7, - control_totals=[ReportingControlTotal.model_validate(item) for item in TOTALS], + control_totals=[ + TypeAdapter(ReportingControlTotal).validate_python(item) for item in TOTALS + ], canonical_content_digest=ReportingCanonicalContentDigest.model_validate(DIGEST), consumer_commit_ref="buyer-ledger-42", ) @@ -1033,7 +1036,7 @@ def test_consumer_billing_mismatch_creates_rejected_receipt() -> None: ReportingObservation( row_count=8, control_totals=[ - ReportingControlTotal.model_validate( + TypeAdapter(ReportingControlTotal).validate_python( { "name": "impressions", "value": "4199", @@ -1041,7 +1044,7 @@ def test_consumer_billing_mismatch_creates_rejected_receipt() -> None: "unit": "impressions", } ), - ReportingControlTotal.model_validate(TOTALS[1]), + TypeAdapter(ReportingControlTotal).validate_python(TOTALS[1]), ], canonical_content_digest=ReportingCanonicalContentDigest.model_validate( {**DIGEST, "value": "d" * 64} @@ -1733,7 +1736,9 @@ async def test_recorded_rejected_checkpoint_is_reinspected() -> None: ), ReportingObservation( row_count=8, - control_totals=[ReportingControlTotal.model_validate(item) for item in TOTALS], + control_totals=[ + TypeAdapter(ReportingControlTotal).validate_python(item) for item in TOTALS + ], canonical_content_digest=ReportingCanonicalContentDigest.model_validate(DIGEST), ), reporting_receipt_id="reporting-receipt:rejected-checkpoint", @@ -1758,7 +1763,9 @@ async def inspect(_: ReportingInspectionContext) -> ReportingObservation: inspections += 1 return ReportingObservation( row_count=7, - control_totals=[ReportingControlTotal.model_validate(item) for item in TOTALS], + control_totals=[ + TypeAdapter(ReportingControlTotal).validate_python(item) for item in TOTALS + ], canonical_content_digest=ReportingCanonicalContentDigest.model_validate(DIGEST), ) diff --git a/tests/test_rootmodel_proxy.py b/tests/test_rootmodel_proxy.py index a4573762b..12f1ac482 100644 --- a/tests/test_rootmodel_proxy.py +++ b/tests/test_rootmodel_proxy.py @@ -16,7 +16,6 @@ DeliveryType, FlatRatePricingOption, Format, - PlatformDeployment, Product, ReportingCapabilities, ) @@ -77,12 +76,12 @@ def test_cpm_variant_attribute_access(product_with_pricing: Product): assert po.pricing_option_id == "cpm_1" -def test_pricing_option_root_still_works(product_with_pricing: Product): - """Existing .root access pattern is unaffected.""" +def test_pricing_option_is_the_arm_itself(product_with_pricing: Product): + """A union root publishes its arms, so an element has no wrapper to unwrap.""" po = product_with_pricing.pricing_options[0] - assert po.root.pricing_option_id == "flat_1" - assert po.root.pricing_model == "flat_rate" + assert type(po).__name__ == "FlatRatePricingOption" + assert not hasattr(po, "root") def test_pricing_option_iteration(product_with_pricing: Product): @@ -121,13 +120,20 @@ def test_pricing_option_serialization_roundtrip(product_with_pricing: Product): assert restored.pricing_model == "flat_rate" -def test_other_rootmodel_unions_have_getattr(): - """Other RootModel union types also proxy attribute access.""" - from adcp.types.generated_poc.core.deployment import Deployment +def test_retained_rootmodel_unions_have_getattr(): + """A union another union discriminates against keeps its wrapper and its proxy. - dep = PlatformDeployment(type="platform", platform="the-trade-desk", is_live=True) - wrapped = Deployment(root=dep) + ``AssetVariant`` discriminates on ``asset_type``, and both ``VastAsset`` + shapes carry ``asset_type='vast'``, so the wrapper is the one choice that + tag maps to. + """ + from adcp.types.generated_poc.core.assets.asset_union import VastAsset, VastAsset1 - assert wrapped.type == "platform" - assert wrapped.platform == "the-trade-desk" - assert wrapped.is_live is True + inner = VastAsset1( + asset_type="vast", delivery_type="url", asset_id="a-1", url="https://cdn.test/v.xml" + ) + wrapped = VastAsset(root=inner) + + assert wrapped.asset_type == "vast" + assert wrapped.delivery_type == "url" + assert wrapped.asset_id == "a-1" diff --git a/tests/test_seller_agent_dual_emit.py b/tests/test_seller_agent_dual_emit.py index 16b5ab719..ac7a335fc 100644 --- a/tests/test_seller_agent_dual_emit.py +++ b/tests/test_seller_agent_dual_emit.py @@ -24,6 +24,7 @@ from typing import Any import pytest +from pydantic import TypeAdapter from adcp.canonical_formats import ( find_declaration_by_v1_format_id, @@ -87,7 +88,10 @@ def test_v2_to_v1_projection_round_trips_each_product( MUST NOT raise any unexpected advisories. """ for p in seller_agent_products: - declarations = [ProductFormatDeclaration.model_validate(opt) for opt in p["format_options"]] + declarations = [ + TypeAdapter(ProductFormatDeclaration).validate_python(opt) + for opt in p["format_options"] + ] class _Product: product_id = p["product_id"] diff --git a/tests/test_trusted_match_validation.py b/tests/test_trusted_match_validation.py index a9cf39044..fa94e6a0a 100644 --- a/tests/test_trusted_match_validation.py +++ b/tests/test_trusted_match_validation.py @@ -1,5 +1,5 @@ import pytest -from pydantic import ValidationError +from pydantic import TypeAdapter, ValidationError from adcp.types.generated_poc.trusted_match.identity_match_response import ( IdentityMatchResponse, @@ -17,7 +17,7 @@ def test_provider_registration_requires_identity_dimensions_when_identity_match_enabled(): with pytest.raises(ValidationError, match="countries is required"): - TmpProviderRegistration.model_validate( + TypeAdapter(TmpProviderRegistration).validate_python( { "provider_id": "provider_1", "endpoint": "https://example.com", @@ -28,7 +28,7 @@ def test_provider_registration_requires_identity_dimensions_when_identity_match_ def test_provider_registration_rejects_non_https_endpoint(): with pytest.raises(ValidationError, match="endpoint must use https"): - TmpProviderRegistration.model_validate( + TypeAdapter(TmpProviderRegistration).validate_python( { "provider_id": "provider_1", "endpoint": "http://example.com", @@ -38,7 +38,7 @@ def test_provider_registration_rejects_non_https_endpoint(): def test_provider_registration_accepts_valid_identity_registration(): - registration = TmpProviderRegistration.model_validate( + registration = TypeAdapter(TmpProviderRegistration).validate_python( { "provider_id": "provider_1", "endpoint": "https://example.com", diff --git a/tests/type_checks/composing_root_subclassing.py b/tests/type_checks/composing_root_subclassing.py new file mode 100644 index 000000000..99afe75c9 --- /dev/null +++ b/tests/type_checks/composing_root_subclassing.py @@ -0,0 +1,57 @@ +"""Adopter-facing type contract for schema roots that compose. + +A root that composes — a union of models, or one model — is published as what it +composes, so an adopter extends it the way every other generated model is +extended. See https://github.com/adcontextprotocol/adcp-client-python/issues/1077. +""" + +from collections.abc import Sequence + +from pydantic import AnyUrl, ConfigDict, Field, TypeAdapter + +from adcp.types import ( + AccountReference, + AccountReferenceById, + CheckGovernanceRequest, + Deployment, + PlatformDeployment, + Signal, +) + + +class StrictAccountReference(AccountReferenceById): + """An arm of a union root takes extra='forbid'.""" + + model_config = ConfigDict(extra="forbid") + + +class StrictCheckGovernanceRequest(CheckGovernanceRequest): + """A single-model root takes extra='forbid'.""" + + model_config = ConfigDict(extra="forbid") + + +class SellerDeployment(PlatformDeployment): + """One arm of a discriminated union, plus a seller-internal field.""" + + model_config = ConfigDict(extra="forbid") + + scope: str = Field(default="platform-wide", exclude=True) + + +class SellerSignal(Signal): + """The narrowed element type is accepted without a type: ignore.""" + + deployments: Sequence[SellerDeployment] = Field(..., min_length=1) + + +account: AccountReference = StrictAccountReference(account_id="acct-1") +governance: CheckGovernanceRequest = StrictCheckGovernanceRequest( + caller=AnyUrl("https://buyer.example/agent") +) +deployment: Deployment = SellerDeployment(platform="the-trade-desk", is_live=True) + +# A union root validates raw data through an adapter and keeps its discriminator. +validated: Deployment = TypeAdapter(Deployment).validate_python( + {"type": "agent", "agent_url": "https://agent.example/a", "is_live": False} +)