diff --git a/CLAUDE.md b/CLAUDE.md index c768966..401532f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -160,7 +160,7 @@ Edit `scripts/post_generate_fixes.py` and add a new function. The script: - Test that aliases point to correct generated types - Test that aliases are exported from main package -**Current Semantic Aliases:** +**Current Semantic Aliases (Complete Coverage - 30 user-facing types):** - **Preview Renders** (discriminated by `output_format`): - `UrlPreviewRender` = `PreviewRender1` (output_format='url') @@ -189,6 +189,26 @@ Edit `scripts/post_generate_fixes.py` and add a new function. The script: - **Activation Keys** (discriminated by identifier type): - `PropertyIdActivationKey`/`PropertyTagActivationKey` +- **Publisher Properties** (discriminated by `selection_type`): + - `PublisherPropertiesAll` = `PublisherProperties` (selection_type='all') + - `PublisherPropertiesById` = `PublisherProperties4` (selection_type='by_id') + - `PublisherPropertiesByTag` = `PublisherProperties5` (selection_type='by_tag') + - Also exports: `PropertyId`, `PropertyTag` (constraint types) + +- **Deployment Types** (discriminated by `type`): + - `PlatformDeployment` = `Deployment1` (type='platform') + - `AgentDeployment` = `Deployment2` (type='agent') + +- **Destination Types** (discriminated by `type`): + - `PlatformDestination` = `Destination1` (type='platform') + - `AgentDestination` = `Destination2` (type='agent') + +**Internal Types NOT Aliased (15 types):** +- Nested helper types: `Input2`, `Input4`, `Response1`, `Results1`, `Preview1`, etc. +- Package update internals: `Packages1`, `Packages2`, `Packages3` +- Format helpers: `AssetsRequired1`, `ViewThreshold1` +- Internal enums: `Field1`, `Method1` + **Note on Pricing Types:** Pricing option types (`CpcPricingOption`, `CpmAuctionPricingOption`, `CpmFixedRatePricingOption`, etc.) already have clear semantic names from the schema generator, so they don't need aliases. These types now include an `is_fixed` discriminator: @@ -201,11 +221,122 @@ Pricing option types (`CpcPricingOption`, `CpmAuctionPricingOption`, `CpmFixedRa - User-facing discriminated unions used in API calls - Types where the discriminator conveys important semantic meaning - Types where numbered suffixes cause confusion +- Types that appear in union fields of Product, MediaBuy, Package +- Request/Response variants users construct or pattern-match ❌ **DON'T create aliases for:** - Internal helper types not commonly used directly - Types where parent context makes the meaning clear -- Generic helper types (Input1, Parameters2, etc.) +- Generic helper types (Input2, Parameters2, etc.) +- Nested container types in requests/responses +- Internal enums (Field1, Method1) + +## Preventing Direct Imports from generated_poc + +**Goal**: Ensure downstream users NEVER have to import from `generated_poc` or use numbered types. + +### Strategy 1: Complete Aliasing Coverage + +**After regenerating types**, audit for new discriminated unions: + +```bash +# Find all numbered types in generated code +grep -h "^class.*[0-9](" src/adcp/types/generated_poc/*.py | \ + sed 's/class \([^(]*\).*/\1/' | sort -u + +# For each numbered type, determine if it needs an alias: +# 1. Is it used in a Union[] in Product/MediaBuy/Package? → YES, alias it +# 2. Is it a Request/Response variant? → YES, alias it +# 3. Does it have a discriminator field (Literal type)? → Probably YES +# 4. Is it only used internally (Input2, Results1)? → NO, skip it +``` + +### Strategy 2: Automated Detection + +Add `scripts/check_missing_aliases.py`: + +```python +"""Check for user-facing numbered types without semantic aliases.""" +import ast +import re +from pathlib import Path + +def find_discriminated_unions(): + """Find types with discriminator fields that need aliases.""" + generated_dir = Path("src/adcp/types/generated_poc") + numbered_types = set() + + for py_file in generated_dir.glob("*.py"): + content = py_file.read_text() + # Find class definitions ending in digits + matches = re.findall(r"class (\w+\d+)\(", content) + # Check if they have Literal discriminators + for match in matches: + if f"Literal[" in content: # Has discriminator + numbered_types.add(match) + + # Check which are already aliased + aliases_file = Path("src/adcp/types/aliases.py") + aliases_content = aliases_file.read_text() + + missing = [] + for typename in numbered_types: + if f"= {typename}" not in aliases_content: + missing.append(typename) + + return missing + +if __name__ == "__main__": + missing = find_discriminated_unions() + if missing: + print(f"❌ Found {len(missing)} numbered types without aliases:") + for t in missing: + print(f" - {t}") + exit(1) + else: + print("✅ All user-facing types have semantic aliases") +``` + +### Strategy 3: CI Enforcement + +Add to `.github/workflows/ci.yml`: + +```yaml +- name: Check for missing type aliases + run: uv run python scripts/check_missing_aliases.py +``` + +### Strategy 4: Import Linting + +Add custom ruff rule or pre-commit hook: + +```python +# .pre-commit-config.yaml or custom linter +# Detect imports from generated_poc in user code +def check_generated_imports(file_path: str) -> list[str]: + """Warn about direct generated_poc imports.""" + if "test_" in file_path or "aliases.py" in file_path: + return [] # Allow in tests and aliases module + + with open(file_path) as f: + for line_no, line in enumerate(f, 1): + if "from adcp.types.generated_poc" in line: + return [f"{file_path}:{line_no}: Don't import from generated_poc - use semantic aliases"] + return [] +``` + +### Strategy 5: Documentation + +**In type regeneration workflow**, always check: + +1. Run type generation: `uv run python scripts/generate_types.py` +2. Check for new numbered types: `scripts/check_missing_aliases.py` +3. If new types found: + - Determine discriminator field + - Add semantic alias in `aliases.py` + - Export from `__init__.py` + - Add tests in `test_type_aliases.py` +4. Commit with descriptive message explaining the discriminator **Type Checking Best Practices** - Use `TYPE_CHECKING` for optional dependencies to avoid runtime import errors diff --git a/src/adcp/__init__.py b/src/adcp/__init__.py index ded6cd3..dfce843 100644 --- a/src/adcp/__init__.py +++ b/src/adcp/__init__.py @@ -101,6 +101,8 @@ from adcp.types.aliases import ( ActivateSignalErrorResponse, ActivateSignalSuccessResponse, + AgentDeployment, + AgentDestination, BothPreviewRender, BuildCreativeErrorResponse, BuildCreativeSuccessResponse, @@ -111,14 +113,21 @@ InlineDaastAsset, InlineVastAsset, MediaSubAsset, + PlatformDeployment, + PlatformDestination, PreviewCreativeFormatRequest, PreviewCreativeInteractiveResponse, PreviewCreativeManifestRequest, PreviewCreativeStaticResponse, + PropertyId, PropertyIdActivationKey, + PropertyTag, PropertyTagActivationKey, ProvidePerformanceFeedbackErrorResponse, ProvidePerformanceFeedbackSuccessResponse, + PublisherPropertiesAll, + PublisherPropertiesById, + PublisherPropertiesByTag, SyncCreativesErrorResponse, SyncCreativesSuccessResponse, TextSubAsset, @@ -281,6 +290,8 @@ # Semantic type aliases (for better API ergonomics) "ActivateSignalSuccessResponse", "ActivateSignalErrorResponse", + "AgentDeployment", + "AgentDestination", "BothPreviewRender", "BuildCreativeSuccessResponse", "BuildCreativeErrorResponse", @@ -290,14 +301,21 @@ "InlineDaastAsset", "InlineVastAsset", "MediaSubAsset", + "PlatformDeployment", + "PlatformDestination", "PreviewCreativeFormatRequest", "PreviewCreativeManifestRequest", "PreviewCreativeStaticResponse", "PreviewCreativeInteractiveResponse", + "PropertyId", "PropertyIdActivationKey", + "PropertyTag", "PropertyTagActivationKey", "ProvidePerformanceFeedbackSuccessResponse", "ProvidePerformanceFeedbackErrorResponse", + "PublisherPropertiesAll", + "PublisherPropertiesById", + "PublisherPropertiesByTag", "SyncCreativesSuccessResponse", "SyncCreativesErrorResponse", "TextSubAsset", diff --git a/src/adcp/types/__init__.py b/src/adcp/types/__init__.py index 0cc9d8a..6a89c4f 100644 --- a/src/adcp/types/__init__.py +++ b/src/adcp/types/__init__.py @@ -17,6 +17,11 @@ InlineDaastAsset, InlineVastAsset, MediaSubAsset, + PropertyId, + PropertyTag, + PublisherPropertiesAll, + PublisherPropertiesById, + PublisherPropertiesByTag, TextSubAsset, UrlDaastAsset, UrlPreviewRender, @@ -89,6 +94,13 @@ "UrlVastAsset", # Package type aliases "CreatedPackageReference", + # Publisher properties types + "PropertyId", + "PropertyTag", + # Publisher properties aliases + "PublisherPropertiesAll", + "PublisherPropertiesById", + "PublisherPropertiesByTag", # Stable API types (commonly used) "BrandManifest", "Creative", diff --git a/src/adcp/types/aliases.py b/src/adcp/types/aliases.py index c7d52ca..6a3da58 100644 --- a/src/adcp/types/aliases.py +++ b/src/adcp/types/aliases.py @@ -48,6 +48,12 @@ # DAAST assets DaastAsset1, DaastAsset2, + # Deployment types + Deployment1, + Deployment2, + # Destination types + Destination1, + Destination2, # Preview creative requests PreviewCreativeRequest1, PreviewCreativeRequest2, @@ -84,6 +90,21 @@ ) from adcp.types.generated_poc.package import Package as FullPackageInternal +# Import PublisherProperties types and related types from product module +from adcp.types.generated_poc.product import ( + PropertyId, + PropertyTag, +) +from adcp.types.generated_poc.product import ( + PublisherProperties as PublisherPropertiesInternal, +) +from adcp.types.generated_poc.product import ( + PublisherProperties4 as PublisherPropertiesByIdInternal, +) +from adcp.types.generated_poc.product import ( + PublisherProperties5 as PublisherPropertiesByTagInternal, +) + # ============================================================================ # RESPONSE TYPE ALIASES - Success/Error Discriminated Unions # ============================================================================ @@ -252,6 +273,213 @@ Fields: buyer_ref, package_id only """ +# ============================================================================ +# PUBLISHER PROPERTIES ALIASES - Selection Type Discriminated Unions +# ============================================================================ +# The AdCP schemas define PublisherProperties as a discriminated union with +# three variants based on the `selection_type` field: +# +# 1. All Properties (selection_type='all'): +# - Includes all properties from the publisher +# - Only requires publisher_domain +# +# 2. By ID (selection_type='by_id'): +# - Specific properties selected by property_id +# - Requires publisher_domain + property_ids array +# +# 3. By Tag (selection_type='by_tag'): +# - Properties selected by tags +# - Requires publisher_domain + property_tags array +# +# These semantic aliases match the discriminator values and make code more +# readable when constructing or pattern-matching publisher properties. + +PublisherPropertiesAll = PublisherPropertiesInternal +"""Publisher properties covering all properties from the publisher. + +This variant uses selection_type='all' and includes all properties listed +in the publisher's adagents.json file. + +Fields: +- publisher_domain: Domain where adagents.json is hosted +- selection_type: Literal['all'] + +Example: + ```python + from adcp import PublisherPropertiesAll + + props = PublisherPropertiesAll( + publisher_domain="example.com", + selection_type="all" + ) + ``` +""" + +PublisherPropertiesById = PublisherPropertiesByIdInternal +"""Publisher properties selected by specific property IDs. + +This variant uses selection_type='by_id' and specifies an explicit list +of property IDs from the publisher's adagents.json file. + +Fields: +- publisher_domain: Domain where adagents.json is hosted +- selection_type: Literal['by_id'] +- property_ids: List of PropertyId (non-empty) + +Example: + ```python + from adcp import PublisherPropertiesById, PropertyId + + props = PublisherPropertiesById( + publisher_domain="example.com", + selection_type="by_id", + property_ids=[PropertyId("homepage"), PropertyId("sports_section")] + ) + ``` +""" + +PublisherPropertiesByTag = PublisherPropertiesByTagInternal +"""Publisher properties selected by tags. + +This variant uses selection_type='by_tag' and specifies property tags. +The product covers all properties in the publisher's adagents.json that +have these tags. + +Fields: +- publisher_domain: Domain where adagents.json is hosted +- selection_type: Literal['by_tag'] +- property_tags: List of PropertyTag (non-empty) + +Example: + ```python + from adcp import PublisherPropertiesByTag, PropertyTag + + props = PublisherPropertiesByTag( + publisher_domain="example.com", + selection_type="by_tag", + property_tags=[PropertyTag("premium"), PropertyTag("video")] + ) + ``` +""" + +# ============================================================================ +# DEPLOYMENT & DESTINATION ALIASES - Signal Deployment Type Discriminated Unions +# ============================================================================ +# The AdCP schemas define Deployment and Destination as discriminated unions +# with two variants based on the `type` field: +# +# Deployment (where a signal is activated): +# - Platform (type='platform'): DSP platform with platform ID +# - Agent (type='agent'): Sales agent with agent URL +# +# Destination (where a signal can be activated): +# - Platform (type='platform'): Target DSP platform +# - Agent (type='agent'): Target sales agent +# +# These are used in GetSignalsResponse to describe signal availability and +# activation status across different advertising platforms and agents. + +PlatformDeployment = Deployment1 +"""Signal deployment to a DSP platform. + +This variant uses type='platform' for platform-based signal deployments +like The Trade Desk, Amazon DSP, etc. + +Fields: +- type: Literal['platform'] +- platform: Platform identifier (e.g., 'the-trade-desk') +- account: Optional account identifier +- is_live: Whether signal is currently active +- deployed_at: Activation timestamp if live +- activation_key: Targeting key if live and accessible +- estimated_activation_duration_minutes: Time to complete activation + +Example: + ```python + from adcp import PlatformDeployment + + deployment = PlatformDeployment( + type="platform", + platform="the-trade-desk", + account="advertiser-123", + is_live=True, + deployed_at=datetime.now(timezone.utc) + ) + ``` +""" + +AgentDeployment = Deployment2 +"""Signal deployment to a sales agent. + +This variant uses type='agent' for agent-based signal deployments +using agent URLs. + +Fields: +- type: Literal['agent'] +- agent_url: URL identifying the destination agent +- account: Optional account identifier +- is_live: Whether signal is currently active +- deployed_at: Activation timestamp if live +- activation_key: Targeting key if live and accessible +- estimated_activation_duration_minutes: Time to complete activation + +Example: + ```python + from adcp import AgentDeployment + + deployment = AgentDeployment( + type="agent", + agent_url="https://agent.example.com", + is_live=False, + estimated_activation_duration_minutes=30.0 + ) + ``` +""" + +PlatformDestination = Destination1 +"""Available signal destination on a DSP platform. + +This variant uses type='platform' for platform-based signal destinations. + +Fields: +- type: Literal['platform'] +- platform: Platform identifier (e.g., 'the-trade-desk', 'amazon-dsp') +- account: Optional account identifier on the platform + +Example: + ```python + from adcp import PlatformDestination + + destination = PlatformDestination( + type="platform", + platform="the-trade-desk", + account="advertiser-123" + ) + ``` +""" + +AgentDestination = Destination2 +"""Available signal destination via a sales agent. + +This variant uses type='agent' for agent-based signal destinations. + +Fields: +- type: Literal['agent'] +- agent_url: URL identifying the destination agent +- account: Optional account identifier on the agent + +Example: + ```python + from adcp import AgentDestination + + destination = AgentDestination( + type="agent", + agent_url="https://agent.example.com", + account="partner-456" + ) + ``` +""" + # ============================================================================ # EXPORTS # ============================================================================ @@ -300,4 +528,17 @@ # Package type aliases "CreatedPackageReference", "Package", + # Publisher properties types + "PropertyId", + "PropertyTag", + # Publisher properties aliases + "PublisherPropertiesAll", + "PublisherPropertiesById", + "PublisherPropertiesByTag", + # Deployment aliases + "PlatformDeployment", + "AgentDeployment", + # Destination aliases + "PlatformDestination", + "AgentDestination", ] diff --git a/tests/test_type_aliases.py b/tests/test_type_aliases.py index 162aed2..a449a84 100644 --- a/tests/test_type_aliases.py +++ b/tests/test_type_aliases.py @@ -396,3 +396,270 @@ def test_stable_package_export_is_full_package(): assert "budget" in stable_fields assert "pricing_option_id" in stable_fields assert "product_id" in stable_fields + + +def test_publisher_properties_aliases_imports(): + """Test that PublisherProperties aliases can be imported.""" + from adcp import ( + PropertyId, + PropertyTag, + PublisherPropertiesAll, + PublisherPropertiesById, + PublisherPropertiesByTag, + ) + from adcp.types.aliases import ( + PropertyId as AliasPropertyId, + ) + from adcp.types.aliases import ( + PropertyTag as AliasPropertyTag, + ) + from adcp.types.aliases import ( + PublisherPropertiesAll as AliasPublisherPropertiesAll, + ) + from adcp.types.aliases import ( + PublisherPropertiesById as AliasPublisherPropertiesById, + ) + from adcp.types.aliases import ( + PublisherPropertiesByTag as AliasPublisherPropertiesByTag, + ) + + # Verify all import paths work + assert PropertyId is AliasPropertyId + assert PropertyTag is AliasPropertyTag + assert PublisherPropertiesAll is AliasPublisherPropertiesAll + assert PublisherPropertiesById is AliasPublisherPropertiesById + assert PublisherPropertiesByTag is AliasPublisherPropertiesByTag + + +def test_publisher_properties_aliases_point_to_correct_types(): + """Test that PublisherProperties aliases point to the correct generated types.""" + from adcp import PublisherPropertiesAll, PublisherPropertiesById, PublisherPropertiesByTag + from adcp.types.generated_poc.product import ( + PublisherProperties, + PublisherProperties4, + PublisherProperties5, + ) + + # Verify aliases point to correct types + assert PublisherPropertiesAll is PublisherProperties + assert PublisherPropertiesById is PublisherProperties4 + assert PublisherPropertiesByTag is PublisherProperties5 + + # Verify they're different types + assert PublisherPropertiesAll is not PublisherPropertiesById + assert PublisherPropertiesAll is not PublisherPropertiesByTag + assert PublisherPropertiesById is not PublisherPropertiesByTag + + +def test_publisher_properties_aliases_have_correct_discriminators(): + """Test that PublisherProperties aliases have the correct discriminator values.""" + from adcp import PublisherPropertiesAll, PublisherPropertiesById, PublisherPropertiesByTag + + # Check that discriminator field has correct literal type + all_selection_type = PublisherPropertiesAll.__annotations__["selection_type"] + by_id_selection_type = PublisherPropertiesById.__annotations__["selection_type"] + by_tag_selection_type = PublisherPropertiesByTag.__annotations__["selection_type"] + + # Verify the annotations contain Literal types + assert "selection_type" in PublisherPropertiesAll.__annotations__ + assert "selection_type" in PublisherPropertiesById.__annotations__ + assert "selection_type" in PublisherPropertiesByTag.__annotations__ + + +def test_publisher_properties_aliases_can_instantiate(): + """Test that PublisherProperties aliases can be used to create instances.""" + from adcp import ( + PropertyId, + PropertyTag, + PublisherPropertiesAll, + PublisherPropertiesById, + PublisherPropertiesByTag, + ) + + # Create PublisherPropertiesAll + props_all = PublisherPropertiesAll( + publisher_domain="example.com", selection_type="all" + ) + assert props_all.publisher_domain == "example.com" + assert props_all.selection_type == "all" + + # Create PublisherPropertiesById + props_by_id = PublisherPropertiesById( + publisher_domain="example.com", + selection_type="by_id", + property_ids=[PropertyId("homepage"), PropertyId("sports")], + ) + assert props_by_id.publisher_domain == "example.com" + assert props_by_id.selection_type == "by_id" + assert len(props_by_id.property_ids) == 2 + + # Create PublisherPropertiesByTag + props_by_tag = PublisherPropertiesByTag( + publisher_domain="example.com", + selection_type="by_tag", + property_tags=[PropertyTag("premium"), PropertyTag("video")], + ) + assert props_by_tag.publisher_domain == "example.com" + assert props_by_tag.selection_type == "by_tag" + assert len(props_by_tag.property_tags) == 2 + + +def test_publisher_properties_aliases_in_exports(): + """Test that PublisherProperties aliases are properly exported.""" + import adcp + import adcp.types.aliases as aliases_module + + # Check main package exports + assert hasattr(adcp, "PropertyId") + assert hasattr(adcp, "PropertyTag") + assert hasattr(adcp, "PublisherPropertiesAll") + assert hasattr(adcp, "PublisherPropertiesById") + assert hasattr(adcp, "PublisherPropertiesByTag") + + assert "PropertyId" in adcp.__all__ + assert "PropertyTag" in adcp.__all__ + assert "PublisherPropertiesAll" in adcp.__all__ + assert "PublisherPropertiesById" in adcp.__all__ + assert "PublisherPropertiesByTag" in adcp.__all__ + + # Check aliases module exports + assert hasattr(aliases_module, "PropertyId") + assert hasattr(aliases_module, "PropertyTag") + assert hasattr(aliases_module, "PublisherPropertiesAll") + assert hasattr(aliases_module, "PublisherPropertiesById") + assert hasattr(aliases_module, "PublisherPropertiesByTag") + + assert "PropertyId" in aliases_module.__all__ + assert "PropertyTag" in aliases_module.__all__ + assert "PublisherPropertiesAll" in aliases_module.__all__ + assert "PublisherPropertiesById" in aliases_module.__all__ + assert "PublisherPropertiesByTag" in aliases_module.__all__ + + +def test_property_id_and_tag_are_root_models(): + """Test that PropertyId and PropertyTag are properly constrained string types.""" + from adcp import PropertyId, PropertyTag + + # Create valid PropertyId and PropertyTag + prop_id = PropertyId("my_property_id") + prop_tag = PropertyTag("premium") + + # Verify they are created successfully + assert prop_id.root == "my_property_id" + assert prop_tag.root == "premium" + + # PropertyTag should be a subclass of PropertyId + assert issubclass(PropertyTag, PropertyId) + + +def test_deployment_aliases_imports(): + """Test that Deployment aliases can be imported.""" + from adcp import AgentDeployment, PlatformDeployment + from adcp.types.aliases import AgentDeployment as AliasAgentDeployment + from adcp.types.aliases import PlatformDeployment as AliasPlatformDeployment + + # Verify all import paths work + assert PlatformDeployment is AliasPlatformDeployment + assert AgentDeployment is AliasAgentDeployment + + +def test_deployment_aliases_point_to_correct_types(): + """Test that Deployment aliases point to the correct generated types.""" + from adcp import AgentDeployment, PlatformDeployment + from adcp.types.generated_poc.deployment import Deployment1, Deployment2 + + # Verify aliases point to correct types + assert PlatformDeployment is Deployment1 + assert AgentDeployment is Deployment2 + + # Verify they're different types + assert PlatformDeployment is not AgentDeployment + + +def test_deployment_aliases_can_instantiate(): + """Test that Deployment aliases can be used to create instances.""" + from adcp import AgentDeployment, PlatformDeployment + from datetime import datetime, timezone + + # Create PlatformDeployment + platform_deployment = PlatformDeployment( + type="platform", platform="the-trade-desk", is_live=True + ) + assert platform_deployment.type == "platform" + assert platform_deployment.platform == "the-trade-desk" + assert platform_deployment.is_live is True + + # Create AgentDeployment + agent_deployment = AgentDeployment( + type="agent", agent_url="https://agent.example.com", is_live=False + ) + assert agent_deployment.type == "agent" + assert str(agent_deployment.agent_url) == "https://agent.example.com/" + assert agent_deployment.is_live is False + + +def test_destination_aliases_imports(): + """Test that Destination aliases can be imported.""" + from adcp import AgentDestination, PlatformDestination + from adcp.types.aliases import AgentDestination as AliasAgentDestination + from adcp.types.aliases import PlatformDestination as AliasPlatformDestination + + # Verify all import paths work + assert PlatformDestination is AliasPlatformDestination + assert AgentDestination is AliasAgentDestination + + +def test_destination_aliases_point_to_correct_types(): + """Test that Destination aliases point to the correct generated types.""" + from adcp import AgentDestination, PlatformDestination + from adcp.types.generated_poc.destination import Destination1, Destination2 + + # Verify aliases point to correct types + assert PlatformDestination is Destination1 + assert AgentDestination is Destination2 + + # Verify they're different types + assert PlatformDestination is not AgentDestination + + +def test_destination_aliases_can_instantiate(): + """Test that Destination aliases can be used to create instances.""" + from adcp import AgentDestination, PlatformDestination + + # Create PlatformDestination + platform_dest = PlatformDestination(type="platform", platform="amazon-dsp") + assert platform_dest.type == "platform" + assert platform_dest.platform == "amazon-dsp" + + # Create AgentDestination + agent_dest = AgentDestination(type="agent", agent_url="https://agent.example.com") + assert agent_dest.type == "agent" + assert str(agent_dest.agent_url) == "https://agent.example.com/" + + +def test_deployment_destination_aliases_in_exports(): + """Test that Deployment and Destination aliases are properly exported.""" + import adcp + import adcp.types.aliases as aliases_module + + # Check main package exports + assert hasattr(adcp, "PlatformDeployment") + assert hasattr(adcp, "AgentDeployment") + assert hasattr(adcp, "PlatformDestination") + assert hasattr(adcp, "AgentDestination") + + assert "PlatformDeployment" in adcp.__all__ + assert "AgentDeployment" in adcp.__all__ + assert "PlatformDestination" in adcp.__all__ + assert "AgentDestination" in adcp.__all__ + + # Check aliases module exports + assert hasattr(aliases_module, "PlatformDeployment") + assert hasattr(aliases_module, "AgentDeployment") + assert hasattr(aliases_module, "PlatformDestination") + assert hasattr(aliases_module, "AgentDestination") + + assert "PlatformDeployment" in aliases_module.__all__ + assert "AgentDeployment" in aliases_module.__all__ + assert "PlatformDestination" in aliases_module.__all__ + assert "AgentDestination" in aliases_module.__all__