Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/adcp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
BothPreviewRender,
BuildCreativeErrorResponse,
BuildCreativeSuccessResponse,
CreatedPackageReference,
CreateMediaBuyErrorResponse,
CreateMediaBuySuccessResponse,
HtmlPreviewRender,
Expand Down Expand Up @@ -213,6 +214,8 @@
"CreativeManifest",
"MediaBuy",
"Package",
# Package type aliases
"CreatedPackageReference",
# Status enums (for control flow)
"CreativeStatus",
"MediaBuyStatus",
Expand Down
3 changes: 3 additions & 0 deletions src/adcp/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from adcp.types.aliases import (
BothPreviewRender,
CreatedPackageReference,
HtmlPreviewRender,
InlineDaastAsset,
InlineVastAsset,
Expand Down Expand Up @@ -86,6 +87,8 @@
"UrlDaastAsset",
"UrlPreviewRender",
"UrlVastAsset",
# Package type aliases
"CreatedPackageReference",
# Stable API types (commonly used)
"BrandManifest",
"Creative",
Expand Down
2 changes: 1 addition & 1 deletion src/adcp/types/_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
DO NOT EDIT MANUALLY.

Generated from: https://github.com/adcontextprotocol/adcp/tree/main/schemas
Generation date: 2025-11-18 12:11:55 UTC
Generation date: 2025-11-18 12:52:17 UTC
"""
# ruff: noqa: E501, I001
from __future__ import annotations
Expand Down
54 changes: 54 additions & 0 deletions src/adcp/types/aliases.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@
VastAsset2,
)

# Import Package types directly from their modules to avoid collision issues
from adcp.types.generated_poc.create_media_buy_response import (
Package as CreatedPackageInternal,
)
from adcp.types.generated_poc.package import Package as FullPackageInternal

# ============================================================================
# RESPONSE TYPE ALIASES - Success/Error Discriminated Unions
# ============================================================================
Expand Down Expand Up @@ -201,6 +207,51 @@
TextSubAsset = SubAsset2
"""SubAsset for text content (headlines, body text) - asset_kind='text', provides content."""

# ============================================================================
# PACKAGE TYPE ALIASES - Resolving Type Name Collisions
# ============================================================================
# The AdCP schemas define two genuinely different types both named "Package":
#
# 1. Full Package (from package.json schema):
# - Complete operational package with all fields (budget, pricing_option_id, etc.)
# - Used in MediaBuy, update operations, and package management
# - Has 12+ fields for full package configuration
#
# 2. Created Package (from create-media-buy-response.json schema):
# - Minimal response type with only IDs (buyer_ref, package_id)
# - Used in CreateMediaBuy success responses
# - Only 2 fields - represents newly created package references
#
# The code generator's "first wins" collision handling exports the Created Package
# as "Package", shadowing the Full Package. These semantic aliases provide clear,
# unambiguous names for both types.

Package = FullPackageInternal
"""Complete package configuration with all operational fields.

This is the canonical Package type used throughout AdCP for package management.

Used in:
- MediaBuy.packages (list of full package details)
- Update operations (modifying existing packages)
- Package management (creating/configuring packages)

Fields include: budget, pricing_option_id, product_id, status, bid_price,
creative_assignments, format_ids_to_provide, impressions, pacing, targeting_overlay
"""

CreatedPackageReference = CreatedPackageInternal
"""Minimal package reference with only IDs returned after creation.

This is NOT the full Package type - it's a lightweight reference returned
in CreateMediaBuySuccessResponse to indicate which packages were created.

Used in:
- CreateMediaBuySuccessResponse.packages (list of created package references)

Fields: buyer_ref, package_id only
"""

# ============================================================================
# EXPORTS
# ============================================================================
Expand Down Expand Up @@ -246,4 +297,7 @@
# Update media buy responses
"UpdateMediaBuySuccessResponse",
"UpdateMediaBuyErrorResponse",
# Package type aliases
"CreatedPackageReference",
"Package",
]
6 changes: 4 additions & 2 deletions src/adcp/types/stable.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

from __future__ import annotations

# Import all generated types from internal consolidated module
from adcp.types._generated import (
# Core request/response types
ActivateSignalRequest,
Expand Down Expand Up @@ -67,7 +66,6 @@
MarkdownAsset,
MediaBuy,
MediaBuyStatus,
Package,
PackageStatus,
PreviewCreativeRequest,
PreviewCreativeResponse,
Expand All @@ -94,6 +92,10 @@
WebhookAsset,
)

# Import all generated types from internal consolidated module
# Import Package directly from its module to avoid collision with Response Package
from adcp.types.generated_poc.package import Package

# Note: BrandManifest is currently split into BrandManifest1/2 due to upstream schema
# using anyOf incorrectly. This will be fixed upstream to create a single BrandManifest type.
# For now, users should use BrandManifest1 (url required) which is most common.
Expand Down
10 changes: 5 additions & 5 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ async def test_get_products():
"""Test get_products method with mock adapter."""
from unittest.mock import patch

from adcp.types.core import TaskResult, TaskStatus
from adcp.types._generated import GetProductsRequest, GetProductsResponse
from adcp.types.core import TaskResult, TaskStatus

config = AgentConfig(
id="test_agent",
Expand Down Expand Up @@ -229,8 +229,8 @@ async def test_multi_agent_parallel_execution():
"""Test parallel execution across multiple agents."""
from unittest.mock import patch

from adcp.types.core import TaskResult, TaskStatus
from adcp.types._generated import GetProductsRequest
from adcp.types.core import TaskResult, TaskStatus

agents = [
AgentConfig(
Expand Down Expand Up @@ -280,8 +280,8 @@ async def test_list_creative_formats_parses_mcp_response():
import json
from unittest.mock import patch

from adcp.types.core import TaskResult, TaskStatus
from adcp.types._generated import ListCreativeFormatsRequest, ListCreativeFormatsResponse
from adcp.types.core import TaskResult, TaskStatus

config = AgentConfig(
id="creative_agent",
Expand Down Expand Up @@ -330,8 +330,8 @@ async def test_list_creative_formats_parses_a2a_response():
"""Test that list_creative_formats parses A2A dict response into structured response."""
from unittest.mock import patch

from adcp.types.core import TaskResult, TaskStatus
from adcp.types._generated import ListCreativeFormatsRequest, ListCreativeFormatsResponse
from adcp.types.core import TaskResult, TaskStatus

config = AgentConfig(
id="creative_agent",
Expand Down Expand Up @@ -374,8 +374,8 @@ async def test_list_creative_formats_handles_invalid_response():
"""Test that list_creative_formats handles invalid response gracefully."""
from unittest.mock import patch

from adcp.types.core import TaskResult, TaskStatus
from adcp.types._generated import ListCreativeFormatsRequest
from adcp.types.core import TaskResult, TaskStatus

config = AgentConfig(
id="creative_agent",
Expand Down
8 changes: 3 additions & 5 deletions tests/test_discriminated_unions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
InlineDaastAsset,
InlineVastAsset,
MediaSubAsset,
Product,
TextSubAsset,
UrlDaastAsset,
UrlPreviewRender,
Expand All @@ -34,7 +33,6 @@
Deployment2, # Agent
Destination1, # Platform
Destination2, # Agent
PublisherProperties, # selection_type='all'
PublisherProperties4, # selection_type='by_id'
PublisherProperties5, # selection_type='by_tag'
)
Expand Down Expand Up @@ -86,7 +84,7 @@ def test_property_ids_authorization_wrong_type_fails(self):
assert "authorization_type" in error_msg.lower()

def test_property_tags_authorization(self):
"""AuthorizedAgents1 (property_tags variant) requires property_tags and authorization_type."""
"""AuthorizedAgents1 requires property_tags and authorization_type."""
agent = AuthorizedAgents1(
url="https://agent.example.com",
authorized_for="All properties",
Expand All @@ -98,7 +96,7 @@ def test_property_tags_authorization(self):
assert not hasattr(agent, "property_ids")

def test_inline_properties_authorization(self):
"""AuthorizedAgents2 (inline_properties variant) requires properties and authorization_type."""
"""AuthorizedAgents2 requires properties and authorization_type."""
agent = AuthorizedAgents2(
url="https://agent.example.com",
authorized_for="All properties",
Expand Down Expand Up @@ -136,7 +134,7 @@ def test_inline_properties_authorization_from_json(self):
assert len(agent.properties) == 1

def test_publisher_properties_authorization(self):
"""AuthorizedAgents3 (publisher_properties variant) requires publisher_properties and authorization_type."""
"""AuthorizedAgents3 requires publisher_properties and type."""
agent = AuthorizedAgents3(
url="https://agent.example.com",
authorized_for="All properties",
Expand Down
2 changes: 1 addition & 1 deletion tests/test_preview_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

from adcp import ADCPClient
from adcp.types import AgentConfig, Protocol
from adcp.types.core import TaskResult, TaskStatus
from adcp.types._generated import (
CreativeManifest,
Format,
Expand All @@ -19,6 +18,7 @@
PreviewCreativeResponse1,
Product,
)
from adcp.types.core import TaskResult, TaskStatus
from adcp.utils.preview_cache import (
PreviewURLGenerator,
_create_sample_asset,
Expand Down
33 changes: 21 additions & 12 deletions tests/test_public_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,20 @@ def test_client_types_are_exported():

def test_public_api_types_are_pydantic_models():
"""Core types from public API are valid Pydantic models."""
from adcp import Product, Format, MediaBuy, Property, BrandManifest
from adcp import BrandManifest, Format, MediaBuy, Product, Property

types_to_test = [Product, Format, MediaBuy, Property, BrandManifest]

for model_class in types_to_test:
# Should have Pydantic model methods
assert hasattr(model_class, "model_validate"), f"{model_class.__name__} missing model_validate"
assert hasattr(model_class, "model_dump"), f"{model_class.__name__} missing model_dump"
assert hasattr(model_class, "model_validate_json"), f"{model_class.__name__} missing model_validate_json"
assert hasattr(model_class, "model_dump_json"), f"{model_class.__name__} missing model_dump_json"
assert hasattr(model_class, "model_fields"), f"{model_class.__name__} missing model_fields"
name = model_class.__name__
assert hasattr(model_class, "model_validate"), f"{name} missing model_validate"
assert hasattr(model_class, "model_dump"), f"{name} missing model_dump"
assert hasattr(model_class, "model_validate_json"), (
f"{name} missing model_validate_json"
)
assert hasattr(model_class, "model_dump_json"), f"{name} missing model_dump_json"
assert hasattr(model_class, "model_fields"), f"{name} missing model_fields"


def test_product_has_expected_public_fields():
Expand Down Expand Up @@ -158,26 +161,32 @@ def test_format_has_expected_public_fields():

def test_pricing_options_are_discriminated_by_is_fixed():
"""Pricing option types have is_fixed discriminator field."""
from adcp import CpmFixedRatePricingOption, CpmAuctionPricingOption, CpcPricingOption
from adcp import CpcPricingOption, CpmAuctionPricingOption, CpmFixedRatePricingOption

# Fixed-rate options should have is_fixed discriminator
fixed_types = [CpmFixedRatePricingOption, CpcPricingOption]
for pricing_type in fixed_types:
assert "is_fixed" in pricing_type.model_fields, f"{pricing_type.__name__} missing is_fixed discriminator"
name = pricing_type.__name__
assert "is_fixed" in pricing_type.model_fields, (
f"{name} missing is_fixed discriminator"
)

# Auction options should have is_fixed discriminator
auction_types = [CpmAuctionPricingOption]
for pricing_type in auction_types:
assert "is_fixed" in pricing_type.model_fields, f"{pricing_type.__name__} missing is_fixed discriminator"
name = pricing_type.__name__
assert "is_fixed" in pricing_type.model_fields, (
f"{name} missing is_fixed discriminator"
)


def test_semantic_aliases_point_to_discriminated_variants():
"""Semantic aliases successfully construct their respective variants."""
from adcp import (
UrlPreviewRender,
HtmlPreviewRender,
CreateMediaBuySuccessResponse,
CreateMediaBuyErrorResponse,
CreateMediaBuySuccessResponse,
HtmlPreviewRender,
UrlPreviewRender,
)

# URL preview render should accept url output format
Expand Down
2 changes: 1 addition & 1 deletion tests/test_simple_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
import pytest

from adcp.testing import test_agent
from adcp.types.core import TaskResult, TaskStatus
from adcp.types._generated import (
GetProductsResponse,
ListCreativeFormatsResponse,
PreviewCreativeResponse1,
Product,
)
from adcp.types.core import TaskResult, TaskStatus


@pytest.mark.asyncio
Expand Down
Loading