From 7fa9be8f7b06d34b6c890555447003bdc8d6d3aa Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 26 Jan 2026 15:23:23 -0500 Subject: [PATCH] feat: export PricingOption and other union type aliases from main package Export PricingOption, Deployment, Destination, and PublisherProperties union type aliases from the main adcp package. These unions fix mypy list-item errors when constructing objects like Product.pricing_options. The PricingOption union combines all pricing variants (CpmPricingOption, VcpmPricingOption, CpcPricingOption, etc.) into a single type that mypy recognizes, avoiding the RootModel wrapper issues. Closes #120 Co-Authored-By: Claude Opus 4.5 --- src/adcp/__init__.py | 8 ++ src/adcp/types/__init__.py | 4 + src/adcp/types/aliases.py | 55 +++++++++ tests/test_type_aliases.py | 233 +++++++++++++++++++++++++++++++++++++ 4 files changed, 300 insertions(+) diff --git a/src/adcp/__init__.py b/src/adcp/__init__.py index c53d0ef4..209ef573 100644 --- a/src/adcp/__init__.py +++ b/src/adcp/__init__.py @@ -139,6 +139,8 @@ BuildCreativeSuccessResponse, CreateMediaBuyErrorResponse, CreateMediaBuySuccessResponse, + Deployment, + Destination, HtmlPreviewRender, InlineDaastAsset, InlineVastAsset, @@ -149,10 +151,12 @@ PreviewCreativeInteractiveResponse, PreviewCreativeManifestRequest, PreviewCreativeStaticResponse, + PricingOption, PropertyId, PropertyTag, ProvidePerformanceFeedbackErrorResponse, ProvidePerformanceFeedbackSuccessResponse, + PublisherProperties, PublisherPropertiesAll, PublisherPropertiesById, PublisherPropertiesByTag, @@ -362,6 +366,8 @@ def get_adcp_version() -> str: "BuildCreativeErrorResponse", "CreateMediaBuySuccessResponse", "CreateMediaBuyErrorResponse", + "Deployment", + "Destination", "HtmlPreviewRender", "InlineDaastAsset", "InlineVastAsset", @@ -372,10 +378,12 @@ def get_adcp_version() -> str: "PreviewCreativeManifestRequest", "PreviewCreativeStaticResponse", "PreviewCreativeInteractiveResponse", + "PricingOption", "PropertyId", "PropertyTag", "ProvidePerformanceFeedbackSuccessResponse", "ProvidePerformanceFeedbackErrorResponse", + "PublisherProperties", "PublisherPropertiesAll", "PublisherPropertiesById", "PublisherPropertiesByTag", diff --git a/src/adcp/types/__init__.py b/src/adcp/types/__init__.py index 447683b1..5008b413 100644 --- a/src/adcp/types/__init__.py +++ b/src/adcp/types/__init__.py @@ -289,10 +289,12 @@ PreviewCreativeInteractiveResponse, PreviewCreativeManifestRequest, PreviewCreativeStaticResponse, + PricingOption, PropertyId, PropertyTag, ProvidePerformanceFeedbackErrorResponse, ProvidePerformanceFeedbackSuccessResponse, + PublisherProperties, PublisherPropertiesAll, PublisherPropertiesById, PublisherPropertiesByTag, @@ -602,10 +604,12 @@ "PreviewCreativeInteractiveResponse", "PreviewCreativeManifestRequest", "PreviewCreativeStaticResponse", + "PricingOption", "PropertyId", "PropertyTag", "ProvidePerformanceFeedbackErrorResponse", "ProvidePerformanceFeedbackSuccessResponse", + "PublisherProperties", "PublisherPropertiesAll", "PublisherPropertiesById", "PublisherPropertiesByTag", diff --git a/src/adcp/types/aliases.py b/src/adcp/types/aliases.py index d5d176ac..ccfaf4c0 100644 --- a/src/adcp/types/aliases.py +++ b/src/adcp/types/aliases.py @@ -43,6 +43,11 @@ # Build creative responses BuildCreativeResponse1, BuildCreativeResponse2, + CpcPricingOption, + CpcvPricingOption, + CpmPricingOption, + CppPricingOption, + CpvPricingOption, # Create media buy responses CreateMediaBuyResponse1, CreateMediaBuyResponse2, @@ -55,6 +60,7 @@ # Destination types Destination1, Destination2, + FlatRatePricingOption, # Preview creative requests PreviewCreativeRequest1, PreviewCreativeRequest2, @@ -86,6 +92,7 @@ # VAST assets VastAsset1, VastAsset2, + VcpmPricingOption, ) from adcp.types._generated import ( PublisherPropertySelector1 as PublisherPropertiesInternal, @@ -688,6 +695,52 @@ def filter_products(props: PublisherProperties) -> None: ``` """ +# ============================================================================ +# PRICING OPTION UNION TYPE - For Type Hints Without RootModel Wrapper +# ============================================================================ +# The generated PricingOption is a RootModel wrapper that mypy doesn't recognize +# as compatible with the individual variant types. This union alias provides a +# way to type-hint pricing options without the wrapper, fixing mypy list-item errors. + +PricingOption = ( + CpmPricingOption + | VcpmPricingOption + | CpcPricingOption + | CpcvPricingOption + | CpvPricingOption + | CppPricingOption + | FlatRatePricingOption +) +"""Union type for all pricing option variants. + +Use this for type hints when constructing Product.pricing_options or any field +that accepts pricing options. This fixes mypy list-item errors that occur when +using the individual variant types. + +Example: + ```python + from adcp.types import Product, CpmPricingOption, PricingOption + + # Type hint for a list of pricing options + def get_pricing(options: list[PricingOption]) -> None: + for opt in options: + print(f"Model: {opt.pricing_model}") + + # Use in Product construction (no more mypy errors!) + product = Product( + product_id="test", + name="Test Product", + pricing_options=[ + CpmPricingOption( + pricing_model="cpm", + floor_price=1.50, + currency="USD" + ) + ] + ) + ``` +""" + # ============================================================================ # EXPORTS # ============================================================================ @@ -759,4 +812,6 @@ def filter_products(props: PublisherProperties) -> None: "AgentDestination", # Destination union "Destination", + # Pricing option union + "PricingOption", ] diff --git a/tests/test_type_aliases.py b/tests/test_type_aliases.py index 95854b96..04e8343d 100644 --- a/tests/test_type_aliases.py +++ b/tests/test_type_aliases.py @@ -572,3 +572,236 @@ def test_deployment_destination_aliases_in_exports(): assert "AgentDeployment" in aliases_module.__all__ assert "PlatformDestination" in aliases_module.__all__ assert "AgentDestination" in aliases_module.__all__ + + +# ============================================================================ +# UNION TYPE ALIASES TESTS +# ============================================================================ + + +def test_pricing_option_union_import(): + """Test that PricingOption union type can be imported.""" + from adcp import PricingOption + from adcp.types.aliases import PricingOption as AliasPricingOption + + # Verify all import paths work + assert PricingOption is AliasPricingOption + + +def test_pricing_option_union_is_union_type(): + """Test that PricingOption is a union type, not a RootModel.""" + import types + + from adcp import PricingOption + + # Should be a Union type, not a class + # In Python 3.10+, unions created with | are types.UnionType + assert isinstance(PricingOption, types.UnionType) + + +def test_pricing_option_union_contains_all_variants(): + """Test that PricingOption union contains all pricing variants.""" + from typing import get_args + + from adcp import ( + CpcPricingOption, + CpcvPricingOption, + CpmPricingOption, + CppPricingOption, + CpvPricingOption, + FlatRatePricingOption, + PricingOption, + VcpmPricingOption, + ) + + # Get all types in the union + union_args = set(get_args(PricingOption)) + + # Verify all pricing option variants are in the union + expected_variants = { + CpmPricingOption, + VcpmPricingOption, + CpcPricingOption, + CpcvPricingOption, + CpvPricingOption, + CppPricingOption, + FlatRatePricingOption, + } + + assert union_args == expected_variants + + +def test_pricing_option_union_exported(): + """Test that PricingOption union is properly exported.""" + import adcp + import adcp.types.aliases as aliases_module + + # Check main package exports + assert hasattr(adcp, "PricingOption") + assert "PricingOption" in adcp.__all__ + + # Check aliases module exports + assert hasattr(aliases_module, "PricingOption") + assert "PricingOption" in aliases_module.__all__ + + +def test_pricing_option_variants_type_compatible(): + """Test that pricing option variants are type-compatible with the union. + + This is the key test from issue #120 - verifying that individual pricing + option types can be used where PricingOption is expected without mypy errors. + """ + from adcp import CpmPricingOption, PricingOption, VcpmPricingOption + + # Create instances of variants (pricing_option_id is required) + cpm = CpmPricingOption( + pricing_option_id="cpm-1", + pricing_model="cpm", + floor_price=1.50, + currency="USD", + ) + vcpm = VcpmPricingOption( + pricing_option_id="vcpm-1", + pricing_model="vcpm", + floor_price=2.00, + currency="USD", + ) + + # These should be type-compatible with PricingOption union + # (mypy will verify this at type-check time, but we can verify instances at runtime) + options: list[PricingOption] = [cpm, vcpm] + + assert len(options) == 2 + assert options[0].pricing_model == "cpm" + assert options[1].pricing_model == "vcpm" + + +def test_publisher_properties_union_import(): + """Test that PublisherProperties union type can be imported.""" + from adcp import PublisherProperties + from adcp.types.aliases import PublisherProperties as AliasPublisherProperties + + # Verify all import paths work + assert PublisherProperties is AliasPublisherProperties + + +def test_publisher_properties_union_is_union_type(): + """Test that PublisherProperties is a union type, not a RootModel.""" + import types + + from adcp import PublisherProperties + + # Should be a Union type, not a class + assert isinstance(PublisherProperties, types.UnionType) + + +def test_publisher_properties_union_contains_all_variants(): + """Test that PublisherProperties union contains all property selector variants.""" + from typing import get_args + + from adcp import ( + PublisherProperties, + PublisherPropertiesAll, + PublisherPropertiesById, + PublisherPropertiesByTag, + ) + + # Get all types in the union + union_args = set(get_args(PublisherProperties)) + + # Verify all publisher properties variants are in the union + expected_variants = { + PublisherPropertiesAll, + PublisherPropertiesById, + PublisherPropertiesByTag, + } + + assert union_args == expected_variants + + +def test_publisher_properties_union_exported(): + """Test that PublisherProperties union is properly exported.""" + import adcp + import adcp.types.aliases as aliases_module + + # Check main package exports + assert hasattr(adcp, "PublisherProperties") + assert "PublisherProperties" in adcp.__all__ + + # Check aliases module exports + assert hasattr(aliases_module, "PublisherProperties") + assert "PublisherProperties" in aliases_module.__all__ + + +def test_deployment_union_import(): + """Test that Deployment union type can be imported.""" + from adcp import Deployment + from adcp.types.aliases import Deployment as AliasDeployment + + # Verify all import paths work + assert Deployment is AliasDeployment + + +def test_deployment_union_is_union_type(): + """Test that Deployment is a union type, not a RootModel.""" + import types + + from adcp import Deployment + + # Should be a Union type, not a class + assert isinstance(Deployment, types.UnionType) + + +def test_deployment_union_contains_all_variants(): + """Test that Deployment union contains all deployment variants.""" + from typing import get_args + + from adcp import AgentDeployment, Deployment, PlatformDeployment + + # Get all types in the union + union_args = set(get_args(Deployment)) + + # Verify all deployment variants are in the union + expected_variants = { + PlatformDeployment, + AgentDeployment, + } + + assert union_args == expected_variants + + +def test_destination_union_import(): + """Test that Destination union type can be imported.""" + from adcp import Destination + from adcp.types.aliases import Destination as AliasDestination + + # Verify all import paths work + assert Destination is AliasDestination + + +def test_destination_union_is_union_type(): + """Test that Destination is a union type, not a RootModel.""" + import types + + from adcp import Destination + + # Should be a Union type, not a class + assert isinstance(Destination, types.UnionType) + + +def test_destination_union_contains_all_variants(): + """Test that Destination union contains all destination variants.""" + from typing import get_args + + from adcp import AgentDestination, Destination, PlatformDestination + + # Get all types in the union + union_args = set(get_args(Destination)) + + # Verify all destination variants are in the union + expected_variants = { + PlatformDestination, + AgentDestination, + } + + assert union_args == expected_variants