From f00d8ca6cc36feb1b044b9f5273d3f35e412d654 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 16 Nov 2025 10:33:41 -0500 Subject: [PATCH 1/5] feat: Add semantic type aliases for discriminated unions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add clear, descriptive type aliases for discriminated union types that were previously only available with auto-generated numbered names (e.g., PreviewRender1, SubAsset1). New semantic aliases: - Preview Renders (output_format discriminator): * UrlPreviewRender = PreviewRender1 (output_format='url') * HtmlPreviewRender = PreviewRender2 (output_format='html') * BothPreviewRender = PreviewRender3 (output_format='both') - VAST Assets (delivery_type discriminator): * UrlVastAsset = VastAsset1 (delivery_type='url') * InlineVastAsset = VastAsset2 (delivery_type='inline') - DAAST Assets (delivery_type discriminator): * UrlDaastAsset = DaastAsset1 (delivery_type='url') * InlineDaastAsset = DaastAsset2 (delivery_type='inline') - SubAssets (asset_kind discriminator): * MediaSubAsset = SubAsset1 (asset_kind='media') * TextSubAsset = SubAsset2 (asset_kind='text') All aliases are: - Exported from adcp.types.aliases - Exported from adcp.types.generated - Re-exported from main adcp package for convenience - Fully tested with comprehensive test coverage This improves code readability by conveying semantic meaning rather than relying on generated numbering. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/adcp/__init__.py | 30 ++++++++-- src/adcp/types/__init__.py | 21 +++++++ src/adcp/types/aliases.py | 56 ++++++++++++++++++- src/adcp/types/generated.py | 30 ++++++++++ tests/test_type_aliases.py | 108 ++++++++++++++++++++++++++++++++++++ 5 files changed, 238 insertions(+), 7 deletions(-) diff --git a/src/adcp/__init__.py b/src/adcp/__init__.py index 4e5cc10..42435ae 100644 --- a/src/adcp/__init__.py +++ b/src/adcp/__init__.py @@ -56,10 +56,15 @@ from adcp.types.aliases import ( ActivateSignalErrorResponse, ActivateSignalSuccessResponse, + BothPreviewRender, BuildCreativeErrorResponse, BuildCreativeSuccessResponse, CreateMediaBuyErrorResponse, CreateMediaBuySuccessResponse, + HtmlPreviewRender, + InlineDaastAsset, + InlineVastAsset, + MediaSubAsset, PreviewCreativeFormatRequest, PreviewCreativeInteractiveResponse, PreviewCreativeManifestRequest, @@ -73,10 +78,14 @@ ProvidePerformanceFeedbackSuccessResponse, SyncCreativesErrorResponse, SyncCreativesSuccessResponse, + TextSubAsset, UpdateMediaBuyErrorResponse, UpdateMediaBuyPackagesRequest, UpdateMediaBuyPropertiesRequest, UpdateMediaBuySuccessResponse, + UrlDaastAsset, + UrlPreviewRender, + UrlVastAsset, ) from adcp.types.core import AgentConfig, Protocol, TaskResult, TaskStatus, WebhookMetadata @@ -219,16 +228,15 @@ # Semantic type aliases (for better API ergonomics) "ActivateSignalSuccessResponse", "ActivateSignalErrorResponse", + "BothPreviewRender", "BuildCreativeSuccessResponse", "BuildCreativeErrorResponse", "CreateMediaBuySuccessResponse", "CreateMediaBuyErrorResponse", - "ProvidePerformanceFeedbackSuccessResponse", - "ProvidePerformanceFeedbackErrorResponse", - "SyncCreativesSuccessResponse", - "SyncCreativesErrorResponse", - "UpdateMediaBuySuccessResponse", - "UpdateMediaBuyErrorResponse", + "HtmlPreviewRender", + "InlineDaastAsset", + "InlineVastAsset", + "MediaSubAsset", "PreviewCreativeFormatRequest", "PreviewCreativeManifestRequest", "PreviewCreativeStaticResponse", @@ -238,6 +246,16 @@ "PreviewRenderIframe", "PropertyIdActivationKey", "PropertyTagActivationKey", + "ProvidePerformanceFeedbackSuccessResponse", + "ProvidePerformanceFeedbackErrorResponse", + "SyncCreativesSuccessResponse", + "SyncCreativesErrorResponse", + "TextSubAsset", + "UpdateMediaBuySuccessResponse", + "UpdateMediaBuyErrorResponse", "UpdateMediaBuyPackagesRequest", "UpdateMediaBuyPropertiesRequest", + "UrlDaastAsset", + "UrlPreviewRender", + "UrlVastAsset", ] diff --git a/src/adcp/types/__init__.py b/src/adcp/types/__init__.py index 30ff4ac..54d773b 100644 --- a/src/adcp/types/__init__.py +++ b/src/adcp/types/__init__.py @@ -13,6 +13,17 @@ TaskStatus, WebhookMetadata, ) +from adcp.types.generated import ( + BothPreviewRender, + HtmlPreviewRender, + InlineDaastAsset, + InlineVastAsset, + MediaSubAsset, + TextSubAsset, + UrlDaastAsset, + UrlPreviewRender, + UrlVastAsset, +) __all__ = [ "AdCPBaseModel", @@ -24,4 +35,14 @@ "Activity", "ActivityType", "DebugInfo", + # Semantic aliases for discriminated unions + "BothPreviewRender", + "HtmlPreviewRender", + "InlineDaastAsset", + "InlineVastAsset", + "MediaSubAsset", + "TextSubAsset", + "UrlDaastAsset", + "UrlPreviewRender", + "UrlVastAsset", ] diff --git a/src/adcp/types/aliases.py b/src/adcp/types/aliases.py index 11feaf9..3d7754d 100644 --- a/src/adcp/types/aliases.py +++ b/src/adcp/types/aliases.py @@ -45,6 +45,9 @@ # Create media buy responses CreateMediaBuyResponse1, CreateMediaBuyResponse2, + # DAAST assets + DaastAsset1, + DaastAsset2, # Preview creative requests PreviewCreativeRequest1, PreviewCreativeRequest2, @@ -58,6 +61,9 @@ # Performance feedback responses ProvidePerformanceFeedbackResponse1, ProvidePerformanceFeedbackResponse2, + # SubAssets + SubAsset1, + SubAsset2, # Sync creatives responses SyncCreativesResponse1, SyncCreativesResponse2, @@ -67,6 +73,9 @@ # Update media buy responses UpdateMediaBuyResponse1, UpdateMediaBuyResponse2, + # VAST assets + VastAsset1, + VastAsset2, ) # ============================================================================ @@ -157,7 +166,7 @@ PreviewCreativeInteractiveResponse = PreviewCreativeResponse2 """Preview response with interactive renders (iframe embedding).""" -# Preview Render Variants +# Preview Render Variants (discriminated by output_format) PreviewRenderImage = PreviewRender1 """Image-based preview render (PNG/JPEG).""" @@ -167,6 +176,41 @@ PreviewRenderIframe = PreviewRender3 """Interactive iframe-based preview render.""" +# More precise aliases based on output_format discriminator +UrlPreviewRender = PreviewRender1 +"""Preview render with output_format='url' - provides preview_url for iframe embedding.""" + +HtmlPreviewRender = PreviewRender2 +"""Preview render with output_format='html' - provides preview_html for direct embedding.""" + +BothPreviewRender = PreviewRender3 +"""Preview render with output_format='both' - provides both preview_url and preview_html.""" + +# ============================================================================ +# ASSET TYPE ALIASES - Delivery & Kind Discriminated Unions +# ============================================================================ + +# VAST Asset Variants (discriminated by delivery_type) +UrlVastAsset = VastAsset1 +"""VAST asset delivered via URL endpoint - delivery_type='url'.""" + +InlineVastAsset = VastAsset2 +"""VAST asset with inline XML content - delivery_type='inline'.""" + +# DAAST Asset Variants (discriminated by delivery_type) +UrlDaastAsset = DaastAsset1 +"""DAAST asset delivered via URL endpoint - delivery_type='url'.""" + +InlineDaastAsset = DaastAsset2 +"""DAAST asset with inline XML content - delivery_type='inline'.""" + +# SubAsset Variants (discriminated by asset_kind) +MediaSubAsset = SubAsset1 +"""SubAsset for media content (images, videos) - asset_kind='media', provides content_uri.""" + +TextSubAsset = SubAsset2 +"""SubAsset for text content (headlines, body text) - asset_kind='text', provides content.""" + # ============================================================================ # EXPORTS # ============================================================================ @@ -178,6 +222,16 @@ # Activation keys "PropertyIdActivationKey", "PropertyTagActivationKey", + # Asset type aliases + "BothPreviewRender", + "HtmlPreviewRender", + "InlineDaastAsset", + "InlineVastAsset", + "MediaSubAsset", + "TextSubAsset", + "UrlDaastAsset", + "UrlPreviewRender", + "UrlVastAsset", # Build creative responses "BuildCreativeSuccessResponse", "BuildCreativeErrorResponse", diff --git a/src/adcp/types/generated.py b/src/adcp/types/generated.py index 7f835b4..7ee53f7 100644 --- a/src/adcp/types/generated.py +++ b/src/adcp/types/generated.py @@ -331,9 +331,39 @@ BrandManifestRef = BrandManifestReference Channels = AdvertisingChannels +# Semantic type aliases for discriminated unions +# These provide clear, descriptive names for numbered types generated by datamodel-code-generator + +# Preview Renders (discriminated by output_format) +UrlPreviewRender = PreviewRender1 # output_format='url' +HtmlPreviewRender = PreviewRender2 # output_format='html' +BothPreviewRender = PreviewRender3 # output_format='both' + +# VAST Assets (discriminated by delivery_type) +UrlVastAsset = VastAsset1 # delivery_type='url' +InlineVastAsset = VastAsset2 # delivery_type='inline' + +# DAAST Assets (discriminated by delivery_type) +UrlDaastAsset = DaastAsset1 # delivery_type='url' +InlineDaastAsset = DaastAsset2 # delivery_type='inline' + +# SubAssets (discriminated by asset_kind) +MediaSubAsset = SubAsset1 # asset_kind='media' +TextSubAsset = SubAsset2 # asset_kind='text' + # Explicit exports __all__ = [ "Action", + # Semantic aliases for discriminated unions + "BothPreviewRender", + "HtmlPreviewRender", + "InlineDaastAsset", + "InlineVastAsset", + "MediaSubAsset", + "TextSubAsset", + "UrlDaastAsset", + "UrlPreviewRender", + "UrlVastAsset", "ActivateSignalRequest", "ActivateSignalResponse", "ActivateSignalResponse1", diff --git a/tests/test_type_aliases.py b/tests/test_type_aliases.py index e7394ef..81e5ace 100644 --- a/tests/test_type_aliases.py +++ b/tests/test_type_aliases.py @@ -12,10 +12,19 @@ from adcp import ( ActivateSignalErrorResponse, ActivateSignalSuccessResponse, + BothPreviewRender, BuildCreativeErrorResponse, BuildCreativeSuccessResponse, CreateMediaBuyErrorResponse, CreateMediaBuySuccessResponse, + HtmlPreviewRender, + InlineDaastAsset, + InlineVastAsset, + MediaSubAsset, + TextSubAsset, + UrlDaastAsset, + UrlPreviewRender, + UrlVastAsset, ) # Test that aliases can also be imported from the aliases module @@ -170,6 +179,10 @@ def test_all_preview_render_aliases_exported(): "PreviewRenderIframe", "PreviewCreativeStaticResponse", "PreviewCreativeInteractiveResponse", + # New semantic aliases based on discriminator + "UrlPreviewRender", + "HtmlPreviewRender", + "BothPreviewRender", ] import adcp.types.aliases as aliases_module @@ -177,3 +190,98 @@ def test_all_preview_render_aliases_exported(): for alias in expected_aliases: assert hasattr(aliases_module, alias), f"Missing alias: {alias}" assert alias in aliases_module.__all__, f"Alias not in __all__: {alias}" + + +def test_all_asset_type_aliases_exported(): + """Test that all asset type aliases are exported.""" + expected_aliases = [ + # VAST assets + "UrlVastAsset", + "InlineVastAsset", + # DAAST assets + "UrlDaastAsset", + "InlineDaastAsset", + # SubAssets + "MediaSubAsset", + "TextSubAsset", + ] + + import adcp.types.aliases as aliases_module + + for alias in expected_aliases: + assert hasattr(aliases_module, alias), f"Missing alias: {alias}" + assert alias in aliases_module.__all__, f"Alias not in __all__: {alias}" + + +def test_discriminated_union_aliases_point_to_correct_types(): + """Test that discriminated union aliases point to the correct generated types.""" + from adcp.types.generated import ( + DaastAsset1, + DaastAsset2, + PreviewRender1, + PreviewRender2, + PreviewRender3, + SubAsset1, + SubAsset2, + VastAsset1, + VastAsset2, + ) + + # Preview renders + assert UrlPreviewRender is PreviewRender1 + assert HtmlPreviewRender is PreviewRender2 + assert BothPreviewRender is PreviewRender3 + + # VAST assets + assert UrlVastAsset is VastAsset1 + assert InlineVastAsset is VastAsset2 + + # DAAST assets + assert UrlDaastAsset is DaastAsset1 + assert InlineDaastAsset is DaastAsset2 + + # SubAssets + assert MediaSubAsset is SubAsset1 + assert TextSubAsset is SubAsset2 + + +def test_semantic_aliases_can_be_imported_from_main_package(): + """Test that new semantic aliases can be imported from the main adcp package.""" + from adcp import ( + BothPreviewRender as MainBothPreviewRender, + ) + from adcp import ( + HtmlPreviewRender as MainHtmlPreviewRender, + ) + from adcp import ( + InlineDaastAsset as MainInlineDaastAsset, + ) + from adcp import ( + InlineVastAsset as MainInlineVastAsset, + ) + from adcp import ( + MediaSubAsset as MainMediaSubAsset, + ) + from adcp import ( + TextSubAsset as MainTextSubAsset, + ) + from adcp import ( + UrlDaastAsset as MainUrlDaastAsset, + ) + from adcp import ( + UrlPreviewRender as MainUrlPreviewRender, + ) + from adcp import ( + UrlVastAsset as MainUrlVastAsset, + ) + + # Verify they match the aliases module exports + assert MainUrlPreviewRender is UrlPreviewRender + assert MainHtmlPreviewRender is HtmlPreviewRender + assert MainBothPreviewRender is BothPreviewRender + assert MainUrlVastAsset is UrlVastAsset + assert MainInlineVastAsset is InlineVastAsset + assert MainUrlDaastAsset is UrlDaastAsset + assert MainInlineDaastAsset is InlineDaastAsset + assert MainMediaSubAsset is MediaSubAsset + assert MainTextSubAsset is TextSubAsset From 6a5a23dfed38a071bb62c14f8b01a4d6e4c33076 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 16 Nov 2025 10:37:07 -0500 Subject: [PATCH 2/5] docs: Document semantic type alias patterns and generated file policy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive documentation for: - How to add semantic type aliases for discriminated unions - Process for creating, exporting, and testing aliases - Current list of all semantic aliases in the codebase - Guidelines for when to create aliases vs when not to - Clarify that generated.py must never be manually modified This ensures future developers understand the alias system and don't accidentally modify auto-generated code. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 960aa89..5b31478 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,7 +19,9 @@ PackageRequest = dict[str, Any] **NEVER Modify Generated Files Directly** -Files in `src/adcp/types/generated_poc/` are auto-generated by `scripts/generate_types.py`. Any manual edits will be lost on regeneration. +Files in `src/adcp/types/generated_poc/` and `src/adcp/types/generated.py` are auto-generated by `scripts/generate_types.py`. Any manual edits will be lost on regeneration. + +**CRITICAL**: Do not add code to `generated.py` or any files in `generated_poc/` directory. These are regenerated from schemas. **Post-Generation Fix System:** @@ -64,6 +66,100 @@ Edit `scripts/post_generate_fixes.py` and add a new function. The script: - Validates fixes were successfully applied - Fails loudly if schema changes break the fix patterns +## Semantic Type Aliases for Discriminated Unions + +**Problem**: The code generator (`datamodel-code-generator`) creates numbered type names for discriminated union variants (e.g., `PreviewRender1`, `PreviewRender2`, `SubAsset1`, `SubAsset2`). While functionally correct, these don't convey semantic meaning. + +**Solution**: Add semantic type aliases in `src/adcp/types/aliases.py` that provide clear, descriptive names based on the discriminator field value. + +**Process for Adding New Semantic Aliases:** + +1. **Identify the discriminated union types** - Look for numbered types (e.g., `TypeName1`, `TypeName2`) in generated files +2. **Determine the discriminator field** - Check the schema/generated code for the `Literal` field that distinguishes variants +3. **Create semantic aliases in `aliases.py`**: + ```python + # Import the generated types + from adcp.types.generated import PreviewRender1, PreviewRender2, PreviewRender3 + + # Create semantic aliases based on discriminator values + UrlPreviewRender = PreviewRender1 # output_format='url' + HtmlPreviewRender = PreviewRender2 # output_format='html' + BothPreviewRender = PreviewRender3 # output_format='both' + ``` + +4. **Add to exports** in `aliases.py`: + ```python + __all__ = [ + ..., + "UrlPreviewRender", + "HtmlPreviewRender", + "BothPreviewRender", + ] + ``` + +5. **Re-export from main package** in `src/adcp/__init__.py`: + ```python + from adcp.types.aliases import ( + ..., + UrlPreviewRender, + HtmlPreviewRender, + BothPreviewRender, + ) + + __all__ = [ + ..., + "UrlPreviewRender", + "HtmlPreviewRender", + "BothPreviewRender", + ] + ``` + +6. **Add comprehensive tests** in `tests/test_type_aliases.py`: + - Test that aliases can be imported + - Test that aliases point to correct generated types + - Test that aliases are exported from main package + +**Current Semantic Aliases:** + +- **Preview Renders** (discriminated by `output_format`): + - `UrlPreviewRender` = `PreviewRender1` (output_format='url') + - `HtmlPreviewRender` = `PreviewRender2` (output_format='html') + - `BothPreviewRender` = `PreviewRender3` (output_format='both') + +- **VAST Assets** (discriminated by `delivery_type`): + - `UrlVastAsset` = `VastAsset1` (delivery_type='url') + - `InlineVastAsset` = `VastAsset2` (delivery_type='inline') + +- **DAAST Assets** (discriminated by `delivery_type`): + - `UrlDaastAsset` = `DaastAsset1` (delivery_type='url') + - `InlineDaastAsset` = `DaastAsset2` (delivery_type='inline') + +- **SubAssets** (discriminated by `asset_kind`): + - `MediaSubAsset` = `SubAsset1` (asset_kind='media') + - `TextSubAsset` = `SubAsset2` (asset_kind='text') + +- **Response Types** (discriminated by success/error): + - Success/Error variants for: ActivateSignal, BuildCreative, CreateMediaBuy, ProvidePerformanceFeedback, SyncCreatives, UpdateMediaBuy + +- **Request Types** (discriminated by operation variant): + - `PreviewCreativeFormatRequest`/`PreviewCreativeManifestRequest` + - `UpdateMediaBuyPackagesRequest`/`UpdateMediaBuyPropertiesRequest` + +- **Activation Keys** (discriminated by identifier type): + - `PropertyIdActivationKey`/`PropertyTagActivationKey` + +**Guidelines for Choosing What to Alias:** + +✅ **DO create aliases for:** +- User-facing discriminated unions used in API calls +- Types where the discriminator conveys important semantic meaning +- Types where numbered suffixes cause confusion + +❌ **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.) + **Type Checking Best Practices** - Use `TYPE_CHECKING` for optional dependencies to avoid runtime import errors - Use `cast()` for JSON deserialization to satisfy mypy's `no-any-return` checks From 3234872fb1467fd7a7ba418a8fbd1b33511c45e9 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 16 Nov 2025 10:50:08 -0500 Subject: [PATCH 3/5] fix: Remove semantic aliases from generated.py (will be overwritten) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The semantic type aliases were incorrectly added to generated.py, which is auto-generated and will be overwritten on next type generation. Moved all semantic alias imports to use aliases.py directly: - src/adcp/types/__init__.py now imports from aliases instead of generated - Removed all alias definitions and exports from generated.py - All functionality preserved through aliases.py This ensures aliases survive type regeneration. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/adcp/types/__init__.py | 22 +++++++++++----------- src/adcp/types/generated.py | 30 ------------------------------ 2 files changed, 11 insertions(+), 41 deletions(-) diff --git a/src/adcp/types/__init__.py b/src/adcp/types/__init__.py index 54d773b..f57eae6 100644 --- a/src/adcp/types/__init__.py +++ b/src/adcp/types/__init__.py @@ -2,6 +2,17 @@ """Type definitions for AdCP client.""" +from adcp.types.aliases import ( + BothPreviewRender, + HtmlPreviewRender, + InlineDaastAsset, + InlineVastAsset, + MediaSubAsset, + TextSubAsset, + UrlDaastAsset, + UrlPreviewRender, + UrlVastAsset, +) from adcp.types.base import AdCPBaseModel from adcp.types.core import ( Activity, @@ -13,17 +24,6 @@ TaskStatus, WebhookMetadata, ) -from adcp.types.generated import ( - BothPreviewRender, - HtmlPreviewRender, - InlineDaastAsset, - InlineVastAsset, - MediaSubAsset, - TextSubAsset, - UrlDaastAsset, - UrlPreviewRender, - UrlVastAsset, -) __all__ = [ "AdCPBaseModel", diff --git a/src/adcp/types/generated.py b/src/adcp/types/generated.py index 7ee53f7..7f835b4 100644 --- a/src/adcp/types/generated.py +++ b/src/adcp/types/generated.py @@ -331,39 +331,9 @@ BrandManifestRef = BrandManifestReference Channels = AdvertisingChannels -# Semantic type aliases for discriminated unions -# These provide clear, descriptive names for numbered types generated by datamodel-code-generator - -# Preview Renders (discriminated by output_format) -UrlPreviewRender = PreviewRender1 # output_format='url' -HtmlPreviewRender = PreviewRender2 # output_format='html' -BothPreviewRender = PreviewRender3 # output_format='both' - -# VAST Assets (discriminated by delivery_type) -UrlVastAsset = VastAsset1 # delivery_type='url' -InlineVastAsset = VastAsset2 # delivery_type='inline' - -# DAAST Assets (discriminated by delivery_type) -UrlDaastAsset = DaastAsset1 # delivery_type='url' -InlineDaastAsset = DaastAsset2 # delivery_type='inline' - -# SubAssets (discriminated by asset_kind) -MediaSubAsset = SubAsset1 # asset_kind='media' -TextSubAsset = SubAsset2 # asset_kind='text' - # Explicit exports __all__ = [ "Action", - # Semantic aliases for discriminated unions - "BothPreviewRender", - "HtmlPreviewRender", - "InlineDaastAsset", - "InlineVastAsset", - "MediaSubAsset", - "TextSubAsset", - "UrlDaastAsset", - "UrlPreviewRender", - "UrlVastAsset", "ActivateSignalRequest", "ActivateSignalResponse", "ActivateSignalResponse1", From 2904baf39f10c844c26cb5b5e96477e864a75eb3 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 16 Nov 2025 11:03:48 -0500 Subject: [PATCH 4/5] refactor: Remove misleading PreviewRender aliases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed the old, misleading aliases for PreviewRender types: - PreviewRenderImage (misleading - actually output_format='url') - PreviewRenderHtml (ambiguous - doesn't clarify it's output_format) - PreviewRenderIframe (misleading - actually output_format='both') Kept only the accurate, discriminator-based aliases: - UrlPreviewRender (output_format='url') - HtmlPreviewRender (output_format='html') - BothPreviewRender (output_format='both') These names accurately reflect the discriminator field values and prevent confusion about what the types represent. Since this is brand new code with no existing users, removing the misleading names now prevents technical debt. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/adcp/__init__.py | 6 ------ src/adcp/types/aliases.py | 14 -------------- tests/test_type_aliases.py | 5 +---- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/src/adcp/__init__.py b/src/adcp/__init__.py index 42435ae..2a95e3d 100644 --- a/src/adcp/__init__.py +++ b/src/adcp/__init__.py @@ -69,9 +69,6 @@ PreviewCreativeInteractiveResponse, PreviewCreativeManifestRequest, PreviewCreativeStaticResponse, - PreviewRenderHtml, - PreviewRenderIframe, - PreviewRenderImage, PropertyIdActivationKey, PropertyTagActivationKey, ProvidePerformanceFeedbackErrorResponse, @@ -241,9 +238,6 @@ "PreviewCreativeManifestRequest", "PreviewCreativeStaticResponse", "PreviewCreativeInteractiveResponse", - "PreviewRenderImage", - "PreviewRenderHtml", - "PreviewRenderIframe", "PropertyIdActivationKey", "PropertyTagActivationKey", "ProvidePerformanceFeedbackSuccessResponse", diff --git a/src/adcp/types/aliases.py b/src/adcp/types/aliases.py index 3d7754d..bd391b0 100644 --- a/src/adcp/types/aliases.py +++ b/src/adcp/types/aliases.py @@ -167,16 +167,6 @@ """Preview response with interactive renders (iframe embedding).""" # Preview Render Variants (discriminated by output_format) -PreviewRenderImage = PreviewRender1 -"""Image-based preview render (PNG/JPEG).""" - -PreviewRenderHtml = PreviewRender2 -"""HTML-based preview render (static markup).""" - -PreviewRenderIframe = PreviewRender3 -"""Interactive iframe-based preview render.""" - -# More precise aliases based on output_format discriminator UrlPreviewRender = PreviewRender1 """Preview render with output_format='url' - provides preview_url for iframe embedding.""" @@ -247,10 +237,6 @@ # Preview creative responses "PreviewCreativeStaticResponse", "PreviewCreativeInteractiveResponse", - # Preview renders - "PreviewRenderImage", - "PreviewRenderHtml", - "PreviewRenderIframe", # Sync creatives responses "SyncCreativesSuccessResponse", "SyncCreativesErrorResponse", diff --git a/tests/test_type_aliases.py b/tests/test_type_aliases.py index 81e5ace..7977811 100644 --- a/tests/test_type_aliases.py +++ b/tests/test_type_aliases.py @@ -174,12 +174,9 @@ def test_all_activation_key_aliases_exported(): def test_all_preview_render_aliases_exported(): """Test that all preview render aliases are exported.""" expected_aliases = [ - "PreviewRenderImage", - "PreviewRenderHtml", - "PreviewRenderIframe", "PreviewCreativeStaticResponse", "PreviewCreativeInteractiveResponse", - # New semantic aliases based on discriminator + # Semantic aliases based on output_format discriminator "UrlPreviewRender", "HtmlPreviewRender", "BothPreviewRender", From 1d1a83cb441e7801bec0509f8dc32b81876aa6ad Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 16 Nov 2025 11:10:06 -0500 Subject: [PATCH 5/5] test: Add comprehensive discriminator tests for semantic type aliases Verifies that semantic aliases match their discriminator field values: - PreviewRender: output_format ('url', 'html', 'both') - VastAsset: delivery_type ('url', 'inline') - DaastAsset: delivery_type ('url', 'inline') - SubAsset: asset_kind ('media', 'text') Tests validate: - Correct discriminator values are present - Correct fields exist for each variant - Wrong discriminator values are rejected - Serialization/deserialization roundtrips work correctly 21 new tests, all passing. --- tests/test_discriminated_unions.py | 260 +++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) diff --git a/tests/test_discriminated_unions.py b/tests/test_discriminated_unions.py index 42d49d4..0b3edd4 100644 --- a/tests/test_discriminated_unions.py +++ b/tests/test_discriminated_unions.py @@ -9,8 +9,17 @@ from adcp import ( ActivateSignalErrorResponse, ActivateSignalSuccessResponse, + BothPreviewRender, CreateMediaBuyErrorResponse, CreateMediaBuySuccessResponse, + HtmlPreviewRender, + InlineDaastAsset, + InlineVastAsset, + MediaSubAsset, + TextSubAsset, + UrlDaastAsset, + UrlPreviewRender, + UrlVastAsset, ) # Keep using generated names for authorization/deployment/destination variants @@ -448,3 +457,254 @@ def test_product_accepts_mixed_publisher_properties(self): assert len(mixed_props) == 2 assert mixed_props[0].selection_type == "by_id" assert mixed_props[1].selection_type == "by_tag" + + +class TestPreviewRenderDiscriminators: + """Test PreviewRender discriminator field values match semantic aliases.""" + + def test_url_preview_render_has_url_discriminator(self): + """UrlPreviewRender has output_format='url'.""" + render = UrlPreviewRender( + render_id="render_1", + role="primary", + output_format="url", + preview_url="https://preview.example.com/creative", + ) + assert render.output_format == "url" + assert hasattr(render, "preview_url") + assert not hasattr(render, "preview_html") + + def test_html_preview_render_has_html_discriminator(self): + """HtmlPreviewRender has output_format='html'.""" + render = HtmlPreviewRender( + render_id="render_1", + role="primary", + output_format="html", + preview_html="
Preview HTML
", + ) + assert render.output_format == "html" + assert hasattr(render, "preview_html") + assert not hasattr(render, "preview_url") + + def test_both_preview_render_has_both_discriminator(self): + """BothPreviewRender has output_format='both'.""" + render = BothPreviewRender( + render_id="render_1", + role="primary", + output_format="both", + preview_url="https://preview.example.com/creative", + preview_html="
Preview HTML
", + ) + assert render.output_format == "both" + assert hasattr(render, "preview_url") + assert hasattr(render, "preview_html") + + def test_url_preview_render_rejects_wrong_discriminator(self): + """UrlPreviewRender rejects output_format='html'.""" + with pytest.raises(ValidationError) as exc_info: + UrlPreviewRender( + render_id="render_1", + role="primary", + output_format="html", # Wrong discriminator value + preview_url="https://preview.example.com/creative", + ) + assert "output_format" in str(exc_info.value).lower() + + def test_html_preview_render_rejects_wrong_discriminator(self): + """HtmlPreviewRender rejects output_format='url'.""" + with pytest.raises(ValidationError) as exc_info: + HtmlPreviewRender( + render_id="render_1", + role="primary", + output_format="url", # Wrong discriminator value + preview_html="
Preview HTML
", + ) + assert "output_format" in str(exc_info.value).lower() + + +class TestVastAssetDiscriminators: + """Test VastAsset discriminator field values match semantic aliases.""" + + def test_url_vast_asset_has_url_discriminator(self): + """UrlVastAsset has delivery_type='url'.""" + asset = UrlVastAsset( + delivery_type="url", + url="https://vast.example.com/ad.xml", + ) + assert asset.delivery_type == "url" + assert hasattr(asset, "url") + assert not hasattr(asset, "vast_xml") + + def test_inline_vast_asset_has_inline_discriminator(self): + """InlineVastAsset has delivery_type='inline'.""" + asset = InlineVastAsset( + delivery_type="inline", + content="...", + ) + assert asset.delivery_type == "inline" + assert hasattr(asset, "content") + assert not hasattr(asset, "url") + + def test_url_vast_asset_rejects_wrong_discriminator(self): + """UrlVastAsset rejects delivery_type='inline'.""" + with pytest.raises(ValidationError) as exc_info: + UrlVastAsset( + delivery_type="inline", # Wrong discriminator value + url="https://vast.example.com/ad.xml", + ) + assert "delivery_type" in str(exc_info.value).lower() + + def test_inline_vast_asset_rejects_wrong_discriminator(self): + """InlineVastAsset rejects delivery_type='url'.""" + with pytest.raises(ValidationError) as exc_info: + InlineVastAsset( + delivery_type="url", # Wrong discriminator value + content="...", + ) + assert "delivery_type" in str(exc_info.value).lower() + + +class TestDaastAssetDiscriminators: + """Test DaastAsset discriminator field values match semantic aliases.""" + + def test_url_daast_asset_has_url_discriminator(self): + """UrlDaastAsset has delivery_type='url'.""" + asset = UrlDaastAsset( + delivery_type="url", + url="https://daast.example.com/ad.xml", + ) + assert asset.delivery_type == "url" + assert hasattr(asset, "url") + assert not hasattr(asset, "content") + + def test_inline_daast_asset_has_inline_discriminator(self): + """InlineDaastAsset has delivery_type='inline'.""" + asset = InlineDaastAsset( + delivery_type="inline", + content="...", + ) + assert asset.delivery_type == "inline" + assert hasattr(asset, "content") + assert not hasattr(asset, "url") + + def test_url_daast_asset_rejects_wrong_discriminator(self): + """UrlDaastAsset rejects delivery_type='inline'.""" + with pytest.raises(ValidationError) as exc_info: + UrlDaastAsset( + delivery_type="inline", # Wrong discriminator value + url="https://daast.example.com/ad.xml", + ) + assert "delivery_type" in str(exc_info.value).lower() + + def test_inline_daast_asset_rejects_wrong_discriminator(self): + """InlineDaastAsset rejects delivery_type='url'.""" + with pytest.raises(ValidationError) as exc_info: + InlineDaastAsset( + delivery_type="url", # Wrong discriminator value + content="...", + ) + assert "delivery_type" in str(exc_info.value).lower() + + +class TestSubAssetDiscriminators: + """Test SubAsset discriminator field values match semantic aliases.""" + + def test_media_sub_asset_has_media_discriminator(self): + """MediaSubAsset has asset_kind='media'.""" + asset = MediaSubAsset( + asset_id="asset_1", + asset_type="logo", + asset_kind="media", + content_uri="https://cdn.example.com/logo.png", + ) + assert asset.asset_kind == "media" + assert hasattr(asset, "content_uri") + assert not hasattr(asset, "content") + + def test_text_sub_asset_has_text_discriminator(self): + """TextSubAsset has asset_kind='text'.""" + asset = TextSubAsset( + asset_id="asset_2", + asset_type="headline", + asset_kind="text", + content="Buy Now!", + ) + assert asset.asset_kind == "text" + assert hasattr(asset, "content") + assert not hasattr(asset, "content_uri") + + def test_media_sub_asset_rejects_wrong_discriminator(self): + """MediaSubAsset rejects asset_kind='text'.""" + with pytest.raises(ValidationError) as exc_info: + MediaSubAsset( + asset_id="asset_1", + asset_type="logo", + asset_kind="text", # Wrong discriminator value + content_uri="https://cdn.example.com/logo.png", + ) + assert "asset_kind" in str(exc_info.value).lower() + + def test_text_sub_asset_rejects_wrong_discriminator(self): + """TextSubAsset rejects asset_kind='media'.""" + with pytest.raises(ValidationError) as exc_info: + TextSubAsset( + asset_id="asset_2", + asset_type="headline", + asset_kind="media", # Wrong discriminator value + content="Buy Now!", + ) + assert "asset_kind" in str(exc_info.value).lower() + + +class TestSemanticAliasDiscriminatorRoundtrips: + """Test that semantic aliases serialize/deserialize with correct discriminators.""" + + def test_url_preview_render_roundtrip(self): + """UrlPreviewRender roundtrips with output_format='url'.""" + original = UrlPreviewRender( + render_id="render_1", + role="primary", + output_format="url", + preview_url="https://preview.example.com/creative", + ) + json_str = original.model_dump_json() + parsed = UrlPreviewRender.model_validate_json(json_str) + assert parsed.output_format == "url" + assert parsed.preview_url == original.preview_url + + def test_url_vast_asset_roundtrip(self): + """UrlVastAsset roundtrips with delivery_type='url'.""" + original = UrlVastAsset( + delivery_type="url", + url="https://vast.example.com/ad.xml", + ) + json_str = original.model_dump_json() + parsed = UrlVastAsset.model_validate_json(json_str) + assert parsed.delivery_type == "url" + assert parsed.url == original.url + + def test_media_sub_asset_roundtrip(self): + """MediaSubAsset roundtrips with asset_kind='media'.""" + original = MediaSubAsset( + asset_id="asset_1", + asset_type="logo", + asset_kind="media", + content_uri="https://cdn.example.com/logo.png", + ) + json_str = original.model_dump_json() + parsed = MediaSubAsset.model_validate_json(json_str) + assert parsed.asset_kind == "media" + assert parsed.content_uri == original.content_uri + + def test_text_sub_asset_roundtrip(self): + """TextSubAsset roundtrips with asset_kind='text'.""" + original = TextSubAsset( + asset_id="asset_2", + asset_type="headline", + asset_kind="text", + content="Buy Now!", + ) + json_str = original.model_dump_json() + parsed = TextSubAsset.model_validate_json(json_str) + assert parsed.asset_kind == "text" + assert parsed.content == original.content