From e53abd4e04ec4c5b535cbedc7846029847942788 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Tue, 18 Nov 2025 16:48:33 -0500 Subject: [PATCH 1/4] feat: add semantic aliases for PublisherProperties discriminated union MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add user-friendly type aliases for the PublisherProperties discriminated union variants to improve developer experience and avoid direct imports from generated_poc. Changes: - Add PublisherPropertiesAll (selection_type='all') - Add PublisherPropertiesById (selection_type='by_id') - Add PublisherPropertiesByTag (selection_type='by_tag') - Export PropertyId and PropertyTag types for use with the variants - Update all export paths (aliases.py, types/__init__.py, __init__.py) - Add comprehensive tests covering imports, type checking, and instantiation These aliases follow the established pattern for other discriminated unions (PreviewRender, VastAsset, etc.) and provide clear, semantic names that match the spec's discriminator values. Example usage: ```python from adcp import PublisherPropertiesByTag, PropertyTag props = PublisherPropertiesByTag( publisher_domain="example.com", selection_type="by_tag", property_tags=[PropertyTag("premium"), PropertyTag("video")] ) ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/adcp/__init__.py | 10 +++ src/adcp/types/__init__.py | 12 +++ src/adcp/types/aliases.py | 105 +++++++++++++++++++++++++ tests/test_type_aliases.py | 154 +++++++++++++++++++++++++++++++++++++ 4 files changed, 281 insertions(+) diff --git a/src/adcp/__init__.py b/src/adcp/__init__.py index ded6cd3..03b2241 100644 --- a/src/adcp/__init__.py +++ b/src/adcp/__init__.py @@ -115,10 +115,15 @@ PreviewCreativeInteractiveResponse, PreviewCreativeManifestRequest, PreviewCreativeStaticResponse, + PropertyId, PropertyIdActivationKey, + PropertyTag, PropertyTagActivationKey, ProvidePerformanceFeedbackErrorResponse, ProvidePerformanceFeedbackSuccessResponse, + PublisherPropertiesAll, + PublisherPropertiesById, + PublisherPropertiesByTag, SyncCreativesErrorResponse, SyncCreativesSuccessResponse, TextSubAsset, @@ -294,10 +299,15 @@ "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..903eb68 100644 --- a/src/adcp/types/aliases.py +++ b/src/adcp/types/aliases.py @@ -84,6 +84,15 @@ ) 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, + PublisherProperties as PublisherPropertiesInternal, + PublisherProperties4 as PublisherPropertiesByIdInternal, + PublisherProperties5 as PublisherPropertiesByTagInternal, +) + # ============================================================================ # RESPONSE TYPE ALIASES - Success/Error Discriminated Unions # ============================================================================ @@ -252,6 +261,95 @@ 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")] + ) + ``` +""" + # ============================================================================ # EXPORTS # ============================================================================ @@ -300,4 +398,11 @@ # Package type aliases "CreatedPackageReference", "Package", + # Publisher properties types + "PropertyId", + "PropertyTag", + # Publisher properties aliases + "PublisherPropertiesAll", + "PublisherPropertiesById", + "PublisherPropertiesByTag", ] diff --git a/tests/test_type_aliases.py b/tests/test_type_aliases.py index 162aed2..004e657 100644 --- a/tests/test_type_aliases.py +++ b/tests/test_type_aliases.py @@ -396,3 +396,157 @@ 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) From 26768914bad261aafdef7aabcb88dd243c2a6b64 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Tue, 18 Nov 2025 17:55:53 -0500 Subject: [PATCH 2/4] feat: add semantic aliases for Deployment and Destination discriminated unions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete the semantic aliasing coverage by adding aliases for signal deployment and destination types used in GetSignalsResponse. Changes: - Add PlatformDeployment (Deployment1, type='platform') - Add AgentDeployment (Deployment2, type='agent') - Add PlatformDestination (Destination1, type='platform') - Add AgentDestination (Destination2, type='agent') - Update all export paths (aliases.py, __init__.py) - Add 7 comprehensive tests covering imports, type checking, and instantiation These aliases complete coverage of all user-facing discriminated unions in the AdCP SDK. Users no longer need to import numbered types or reach into generated_poc for any common operations. Example usage: ```python from adcp import PlatformDeployment, AgentDestination deployment = PlatformDeployment( type="platform", platform="the-trade-desk", is_live=True ) destination = AgentDestination( type="agent", agent_url="https://agent.example.com" ) ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/adcp/__init__.py | 8 +++ src/adcp/types/aliases.py | 130 +++++++++++++++++++++++++++++++++++++ tests/test_type_aliases.py | 113 ++++++++++++++++++++++++++++++++ 3 files changed, 251 insertions(+) diff --git a/src/adcp/__init__.py b/src/adcp/__init__.py index 03b2241..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,6 +113,8 @@ InlineDaastAsset, InlineVastAsset, MediaSubAsset, + PlatformDeployment, + PlatformDestination, PreviewCreativeFormatRequest, PreviewCreativeInteractiveResponse, PreviewCreativeManifestRequest, @@ -286,6 +290,8 @@ # Semantic type aliases (for better API ergonomics) "ActivateSignalSuccessResponse", "ActivateSignalErrorResponse", + "AgentDeployment", + "AgentDestination", "BothPreviewRender", "BuildCreativeSuccessResponse", "BuildCreativeErrorResponse", @@ -295,6 +301,8 @@ "InlineDaastAsset", "InlineVastAsset", "MediaSubAsset", + "PlatformDeployment", + "PlatformDestination", "PreviewCreativeFormatRequest", "PreviewCreativeManifestRequest", "PreviewCreativeStaticResponse", diff --git a/src/adcp/types/aliases.py b/src/adcp/types/aliases.py index 903eb68..f6dbdb9 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, @@ -350,6 +356,124 @@ ``` """ +# ============================================================================ +# 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 # ============================================================================ @@ -405,4 +529,10 @@ "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 004e657..a449a84 100644 --- a/tests/test_type_aliases.py +++ b/tests/test_type_aliases.py @@ -550,3 +550,116 @@ def test_property_id_and_tag_are_root_models(): # 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__ From b2e6bbfbf4d1f94c6e956d63d03031dcd85f1013 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Tue, 18 Nov 2025 18:42:49 -0500 Subject: [PATCH 3/4] docs: document comprehensive semantic aliasing strategy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add detailed documentation on: - Complete coverage of all 30 user-facing discriminated union aliases - List of 15 internal types that don't need aliases - Five-strategy approach to prevent numbered type usage: 1. Complete aliasing coverage checklist 2. Automated detection script (check_missing_aliases.py) 3. CI enforcement in workflows 4. Import linting with pre-commit hooks 5. Type regeneration workflow documentation This ensures downstream users never need to import from generated_poc or use numbered types, maintaining a clean and stable public API. The documentation includes: - Updated list of all current semantic aliases - Guidelines for when to alias vs skip - Bash commands for auditing numbered types - Python script templates for automation - CI/CD integration examples 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 2 deletions(-) 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 From 26d070e132ed9b2ccb7c65bf42c4fbbe276b96bf Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Tue, 18 Nov 2025 19:07:39 -0500 Subject: [PATCH 4/4] style: fix import sorting in aliases.py --- src/adcp/types/aliases.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/adcp/types/aliases.py b/src/adcp/types/aliases.py index f6dbdb9..6a3da58 100644 --- a/src/adcp/types/aliases.py +++ b/src/adcp/types/aliases.py @@ -94,8 +94,14 @@ 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, )