diff --git a/README.md b/README.md index 2c0535c..2805f4a 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ print(products.products[0].name) **Standard API** (`client.*`) - Recommended for production: ```python from adcp.testing import test_agent -from adcp.types.generated import GetProductsRequest +from adcp import GetProductsRequest # Explicit request objects and TaskResult wrapper request = GetProductsRequest(brief='Coffee brands') @@ -85,6 +85,8 @@ Pre-configured agents (all include `.simple` accessor): See [examples/simple_api_demo.py](examples/simple_api_demo.py) for a complete comparison. +> **Tip**: Import types from the main `adcp` package (e.g., `from adcp import GetProductsRequest`) rather than `adcp.types.generated` for better API stability. + ## Quick Start: Distributed Operations For production use, configure your own agents: @@ -148,7 +150,7 @@ from adcp.testing import ( test_agent_no_auth, test_agent_a2a_no_auth, creative_agent, test_agent_client, create_test_agent ) -from adcp.types.generated import GetProductsRequest, PreviewCreativeRequest +from adcp import GetProductsRequest, PreviewCreativeRequest # 1. Single agent with authentication (MCP) result = await test_agent.get_products( @@ -204,6 +206,7 @@ client = ADCPClient(config) - **Auto-detection**: Automatically detect which protocol an agent uses ### Type Safety + Full type hints with Pydantic validation and auto-generated types from the AdCP spec: ```python @@ -219,6 +222,40 @@ if result.success: print(product.name, product.pricing_options) # Full IDE autocomplete! ``` +#### Semantic Type Aliases + +For discriminated union types (success/error responses), use semantic aliases for clearer code: + +```python +from adcp import ( + CreateMediaBuySuccessResponse, # Clear: this is the success case + CreateMediaBuyErrorResponse, # Clear: this is the error case +) + +def handle_response( + response: CreateMediaBuySuccessResponse | CreateMediaBuyErrorResponse +) -> None: + if isinstance(response, CreateMediaBuySuccessResponse): + print(f"✅ Media buy created: {response.media_buy_id}") + else: + print(f"❌ Errors: {response.errors}") +``` + +**Available semantic aliases:** +- Response types: `*SuccessResponse` / `*ErrorResponse` (e.g., `CreateMediaBuySuccessResponse`) +- Request variants: `*FormatRequest` / `*ManifestRequest` (e.g., `PreviewCreativeFormatRequest`) +- Preview renders: `PreviewRenderImage` / `PreviewRenderHtml` / `PreviewRenderIframe` +- Activation keys: `PropertyIdActivationKey` / `PropertyTagActivationKey` + +See `examples/type_aliases_demo.py` for more examples. + +**Import guidelines:** +- ✅ **DO**: Import from main package: `from adcp import GetProductsRequest` +- ✅ **DO**: Use semantic aliases: `from adcp import CreateMediaBuySuccessResponse` +- ⚠️ **AVOID**: Import from internal modules: `from adcp.types.generated import CreateMediaBuyResponse1` + +The main package exports provide a stable API while internal generated types may change. + ### Multi-Agent Operations Execute across multiple agents simultaneously: diff --git a/examples/type_aliases_demo.py b/examples/type_aliases_demo.py new file mode 100644 index 0000000..e6d536f --- /dev/null +++ b/examples/type_aliases_demo.py @@ -0,0 +1,58 @@ +"""Demonstration of ergonomic type aliases. + +This example shows how to use the semantic type aliases for better code clarity. +""" + +from __future__ import annotations + +# Import semantic aliases from the main package +from adcp import ( + CreateMediaBuyErrorResponse, + CreateMediaBuySuccessResponse, +) + + +def handle_create_media_buy_response( + response: CreateMediaBuySuccessResponse | CreateMediaBuyErrorResponse, +) -> None: + """Handle a create media buy response with semantic types. + + Before ergonomic aliases (unclear): + response: CreateMediaBuyResponse1 | CreateMediaBuyResponse2 + + After ergonomic aliases (clear): + response: CreateMediaBuySuccessResponse | CreateMediaBuyErrorResponse + + The semantic names make it immediately clear what each variant represents. + """ + # Type narrowing with isinstance works perfectly + if isinstance(response, CreateMediaBuySuccessResponse): + print(f"✅ Success! Media buy created: {response.media_buy_id}") + print(f" Buyer reference: {response.buyer_ref}") + print(f" Packages: {len(response.packages)}") + elif isinstance(response, CreateMediaBuyErrorResponse): + print("❌ Error creating media buy:") + for error in response.errors: + print(f" - {error.code}: {error.message}") + + +# Example usage +if __name__ == "__main__": + # Success case + success = CreateMediaBuySuccessResponse( + media_buy_id="mb_12345", + buyer_ref="ref_67890", + packages=[], + ) + handle_create_media_buy_response(success) + + print() + + # Error case + error = CreateMediaBuyErrorResponse( + errors=[ + {"code": "invalid_budget", "message": "Budget must be at least $100"}, + {"code": "missing_dates", "message": "Start and end dates required"}, + ] + ) + handle_create_media_buy_response(error) diff --git a/pyproject.toml b/pyproject.toml index 1274c9d..ebff84c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,9 @@ Issues = "https://github.com/adcontextprotocol/adcp-client-python/issues" [tool.setuptools.packages.find] where = ["src"] +[tool.setuptools.package-data] +adcp = ["py.typed"] + [tool.black] line-length = 100 target-version = ["py310", "py311", "py312"] diff --git a/src/adcp/__init__.py b/src/adcp/__init__.py index f1b4683..f7d35fb 100644 --- a/src/adcp/__init__.py +++ b/src/adcp/__init__.py @@ -50,8 +50,75 @@ ) # Import all generated types - users can import what they need from adcp.types.generated -from adcp.types import generated +from adcp.types import aliases, generated + +# Re-export semantic type aliases for better ergonomics +from adcp.types.aliases import ( + ActivateSignalErrorResponse, + ActivateSignalSuccessResponse, + BuildCreativeErrorResponse, + BuildCreativeSuccessResponse, + CreateMediaBuyErrorResponse, + CreateMediaBuySuccessResponse, + PreviewCreativeFormatRequest, + PreviewCreativeInteractiveResponse, + PreviewCreativeManifestRequest, + PreviewCreativeStaticResponse, + PreviewRenderHtml, + PreviewRenderIframe, + PreviewRenderImage, + PropertyIdActivationKey, + PropertyTagActivationKey, + ProvidePerformanceFeedbackErrorResponse, + ProvidePerformanceFeedbackSuccessResponse, + SyncCreativesErrorResponse, + SyncCreativesSuccessResponse, + UpdateMediaBuyErrorResponse, + UpdateMediaBuyPackagesRequest, + UpdateMediaBuyPropertiesRequest, + UpdateMediaBuySuccessResponse, +) from adcp.types.core import AgentConfig, Protocol, TaskResult, TaskStatus, WebhookMetadata + +# Re-export commonly-used request/response types for convenience +# Users should import from main package (e.g., `from adcp import GetProductsRequest`) +# rather than internal modules for better API stability +from adcp.types.generated import ( + # Audience & Targeting + ActivateSignalRequest, + ActivateSignalResponse, + # Creative Operations + BuildCreativeRequest, + BuildCreativeResponse, + # Media Buy Operations + CreateMediaBuyRequest, + CreateMediaBuyResponse, + # Common data types + Error, + Format, + GetMediaBuyDeliveryRequest, + GetMediaBuyDeliveryResponse, + GetProductsRequest, + GetProductsResponse, + GetSignalsRequest, + GetSignalsResponse, + ListAuthorizedPropertiesRequest, + ListAuthorizedPropertiesResponse, + ListCreativeFormatsRequest, + ListCreativeFormatsResponse, + ListCreativesRequest, + ListCreativesResponse, + PreviewCreativeRequest, + PreviewCreativeResponse, + Product, + Property, + ProvidePerformanceFeedbackRequest, + ProvidePerformanceFeedbackResponse, + SyncCreativesRequest, + SyncCreativesResponse, + UpdateMediaBuyRequest, + UpdateMediaBuyResponse, +) from adcp.types.generated import TaskStatus as GeneratedTaskStatus from adcp.validation import ( ValidationError, @@ -73,6 +140,37 @@ "TaskResult", "TaskStatus", "WebhookMetadata", + # Common request/response types (re-exported for convenience) + "CreateMediaBuyRequest", + "CreateMediaBuyResponse", + "GetMediaBuyDeliveryRequest", + "GetMediaBuyDeliveryResponse", + "GetProductsRequest", + "GetProductsResponse", + "UpdateMediaBuyRequest", + "UpdateMediaBuyResponse", + "BuildCreativeRequest", + "BuildCreativeResponse", + "ListCreativeFormatsRequest", + "ListCreativeFormatsResponse", + "ListCreativesRequest", + "ListCreativesResponse", + "PreviewCreativeRequest", + "PreviewCreativeResponse", + "SyncCreativesRequest", + "SyncCreativesResponse", + "ActivateSignalRequest", + "ActivateSignalResponse", + "GetSignalsRequest", + "GetSignalsResponse", + "ListAuthorizedPropertiesRequest", + "ListAuthorizedPropertiesResponse", + "ProvidePerformanceFeedbackRequest", + "ProvidePerformanceFeedbackResponse", + "Error", + "Format", + "Product", + "Property", # Adagents validation "fetch_adagents", "verify_agent_authorization", @@ -114,7 +212,32 @@ "validate_agent_authorization", "validate_product", "validate_publisher_properties_item", - # Generated types module + # Generated types modules "generated", + "aliases", "GeneratedTaskStatus", + # Semantic type aliases (for better API ergonomics) + "ActivateSignalSuccessResponse", + "ActivateSignalErrorResponse", + "BuildCreativeSuccessResponse", + "BuildCreativeErrorResponse", + "CreateMediaBuySuccessResponse", + "CreateMediaBuyErrorResponse", + "ProvidePerformanceFeedbackSuccessResponse", + "ProvidePerformanceFeedbackErrorResponse", + "SyncCreativesSuccessResponse", + "SyncCreativesErrorResponse", + "UpdateMediaBuySuccessResponse", + "UpdateMediaBuyErrorResponse", + "PreviewCreativeFormatRequest", + "PreviewCreativeManifestRequest", + "PreviewCreativeStaticResponse", + "PreviewCreativeInteractiveResponse", + "PreviewRenderImage", + "PreviewRenderHtml", + "PreviewRenderIframe", + "PropertyIdActivationKey", + "PropertyTagActivationKey", + "UpdateMediaBuyPackagesRequest", + "UpdateMediaBuyPropertiesRequest", ] diff --git a/src/adcp/py.typed b/src/adcp/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/adcp/types/aliases.py b/src/adcp/types/aliases.py new file mode 100644 index 0000000..11feaf9 --- /dev/null +++ b/src/adcp/types/aliases.py @@ -0,0 +1,209 @@ +"""Semantic type aliases for generated AdCP types. + +This module provides user-friendly aliases for generated types where the +auto-generated names don't match user expectations from reading the spec. + +The code generator (datamodel-code-generator) creates numbered suffixes for +discriminated union variants (e.g., Response1, Response2), but users expect +semantic names (e.g., SuccessResponse, ErrorResponse). + +Categories of aliases: + +1. Discriminated Union Response Variants + - Success/Error cases for API responses + - Named to match the semantic meaning from the spec + +2. Preview/Render Types + - Input/Output/Request/Response variants + - Numbered types mapped to their semantic purpose + +3. Activation Keys + - Signal activation key variants + +DO NOT EDIT the generated types directly - they are regenerated from schemas. +Add aliases here for any types where the generated name is unclear. + +Validation: +This module will raise ImportError at import time if any of the referenced +generated types do not exist. This ensures that schema changes are caught +immediately rather than at runtime when users try to use the aliases. +""" + +from __future__ import annotations + +# Import all generated types that need semantic aliases +from adcp.types.generated import ( + # Activation responses + ActivateSignalResponse1, + ActivateSignalResponse2, + # Activation keys + ActivationKey1, + ActivationKey2, + # Build creative responses + BuildCreativeResponse1, + BuildCreativeResponse2, + # Create media buy responses + CreateMediaBuyResponse1, + CreateMediaBuyResponse2, + # Preview creative requests + PreviewCreativeRequest1, + PreviewCreativeRequest2, + # Preview creative responses + PreviewCreativeResponse1, + PreviewCreativeResponse2, + # Preview renders + PreviewRender1, + PreviewRender2, + PreviewRender3, + # Performance feedback responses + ProvidePerformanceFeedbackResponse1, + ProvidePerformanceFeedbackResponse2, + # Sync creatives responses + SyncCreativesResponse1, + SyncCreativesResponse2, + # Update media buy requests + UpdateMediaBuyRequest1, + UpdateMediaBuyRequest2, + # Update media buy responses + UpdateMediaBuyResponse1, + UpdateMediaBuyResponse2, +) + +# ============================================================================ +# RESPONSE TYPE ALIASES - Success/Error Discriminated Unions +# ============================================================================ +# These are atomic operations where the response is EITHER success OR error, +# never both. The numbered suffixes from the generator don't convey this +# critical semantic distinction. + +# Activate Signal Response Variants +ActivateSignalSuccessResponse = ActivateSignalResponse1 +"""Success response - signal activation succeeded.""" + +ActivateSignalErrorResponse = ActivateSignalResponse2 +"""Error response - signal activation failed.""" + +# Build Creative Response Variants +BuildCreativeSuccessResponse = BuildCreativeResponse1 +"""Success response - creative built successfully, manifest returned.""" + +BuildCreativeErrorResponse = BuildCreativeResponse2 +"""Error response - creative build failed, no manifest created.""" + +# Create Media Buy Response Variants +CreateMediaBuySuccessResponse = CreateMediaBuyResponse1 +"""Success response - media buy created successfully with media_buy_id.""" + +CreateMediaBuyErrorResponse = CreateMediaBuyResponse2 +"""Error response - media buy creation failed, no media buy created.""" + +# Performance Feedback Response Variants +ProvidePerformanceFeedbackSuccessResponse = ProvidePerformanceFeedbackResponse1 +"""Success response - performance feedback accepted.""" + +ProvidePerformanceFeedbackErrorResponse = ProvidePerformanceFeedbackResponse2 +"""Error response - performance feedback rejected.""" + +# Sync Creatives Response Variants +SyncCreativesSuccessResponse = SyncCreativesResponse1 +"""Success response - sync operation processed creatives.""" + +SyncCreativesErrorResponse = SyncCreativesResponse2 +"""Error response - sync operation failed.""" + +# Update Media Buy Response Variants +UpdateMediaBuySuccessResponse = UpdateMediaBuyResponse1 +"""Success response - media buy updated successfully.""" + +UpdateMediaBuyErrorResponse = UpdateMediaBuyResponse2 +"""Error response - media buy update failed, no changes applied.""" + +# ============================================================================ +# REQUEST TYPE ALIASES - Operation Variants +# ============================================================================ + +# Preview Creative Request Variants +PreviewCreativeFormatRequest = PreviewCreativeRequest1 +"""Preview request using format_id to identify creative format.""" + +PreviewCreativeManifestRequest = PreviewCreativeRequest2 +"""Preview request using creative_manifest_url to identify creative.""" + +# Update Media Buy Request Variants +UpdateMediaBuyPackagesRequest = UpdateMediaBuyRequest1 +"""Update request modifying packages in the media buy.""" + +UpdateMediaBuyPropertiesRequest = UpdateMediaBuyRequest2 +"""Update request modifying media buy properties (not packages).""" + +# ============================================================================ +# ACTIVATION KEY ALIASES +# ============================================================================ + +PropertyIdActivationKey = ActivationKey1 +"""Activation key using property_id for identification.""" + +PropertyTagActivationKey = ActivationKey2 +"""Activation key using property_tags for identification.""" + +# ============================================================================ +# PREVIEW/RENDER TYPE ALIASES +# ============================================================================ + +# Preview Creative Response Variants +PreviewCreativeStaticResponse = PreviewCreativeResponse1 +"""Preview response with static renders (image/HTML snapshots).""" + +PreviewCreativeInteractiveResponse = PreviewCreativeResponse2 +"""Preview response with interactive renders (iframe embedding).""" + +# Preview Render Variants +PreviewRenderImage = PreviewRender1 +"""Image-based preview render (PNG/JPEG).""" + +PreviewRenderHtml = PreviewRender2 +"""HTML-based preview render (static markup).""" + +PreviewRenderIframe = PreviewRender3 +"""Interactive iframe-based preview render.""" + +# ============================================================================ +# EXPORTS +# ============================================================================ + +__all__ = [ + # Activation responses + "ActivateSignalSuccessResponse", + "ActivateSignalErrorResponse", + # Activation keys + "PropertyIdActivationKey", + "PropertyTagActivationKey", + # Build creative responses + "BuildCreativeSuccessResponse", + "BuildCreativeErrorResponse", + # Create media buy responses + "CreateMediaBuySuccessResponse", + "CreateMediaBuyErrorResponse", + # Performance feedback responses + "ProvidePerformanceFeedbackSuccessResponse", + "ProvidePerformanceFeedbackErrorResponse", + # Preview creative requests + "PreviewCreativeFormatRequest", + "PreviewCreativeManifestRequest", + # Preview creative responses + "PreviewCreativeStaticResponse", + "PreviewCreativeInteractiveResponse", + # Preview renders + "PreviewRenderImage", + "PreviewRenderHtml", + "PreviewRenderIframe", + # Sync creatives responses + "SyncCreativesSuccessResponse", + "SyncCreativesErrorResponse", + # Update media buy requests + "UpdateMediaBuyPackagesRequest", + "UpdateMediaBuyPropertiesRequest", + # Update media buy responses + "UpdateMediaBuySuccessResponse", + "UpdateMediaBuyErrorResponse", +] diff --git a/tests/test_discriminated_unions.py b/tests/test_discriminated_unions.py index 1d8c098..42d49d4 100644 --- a/tests/test_discriminated_unions.py +++ b/tests/test_discriminated_unions.py @@ -5,15 +5,21 @@ import pytest from pydantic import ValidationError +# Use semantic aliases for response types +from adcp import ( + ActivateSignalErrorResponse, + ActivateSignalSuccessResponse, + CreateMediaBuyErrorResponse, + CreateMediaBuySuccessResponse, +) + +# Keep using generated names for authorization/deployment/destination variants +# since these don't have semantic aliases yet from adcp.types.generated import ( - ActivateSignalResponse1, # Success - ActivateSignalResponse2, # Error AuthorizedAgents, # property_ids variant AuthorizedAgents1, # property_tags variant AuthorizedAgents2, # inline_properties variant AuthorizedAgents3, # publisher_properties variant - CreateMediaBuyResponse1, # Success - CreateMediaBuyResponse2, # Error Deployment1, # Platform Deployment2, # Agent Destination1, # Platform @@ -109,8 +115,8 @@ class TestResponseUnions: """Test discriminated union response types.""" def test_create_media_buy_success_variant(self): - """CreateMediaBuyResponse1 (success) should validate with required fields.""" - success = CreateMediaBuyResponse1( + """CreateMediaBuySuccessResponse should validate with required fields.""" + success = CreateMediaBuySuccessResponse( media_buy_id="mb_123", buyer_ref="ref_456", packages=[], @@ -120,8 +126,8 @@ def test_create_media_buy_success_variant(self): assert not hasattr(success, "errors") def test_create_media_buy_error_variant(self): - """CreateMediaBuyResponse2 (error) should validate with errors field.""" - error = CreateMediaBuyResponse2( + """CreateMediaBuyErrorResponse should validate with errors field.""" + error = CreateMediaBuyErrorResponse( errors=[{"code": "invalid_budget", "message": "Budget too low"}], ) assert len(error.errors) == 1 @@ -129,16 +135,16 @@ def test_create_media_buy_error_variant(self): assert not hasattr(error, "media_buy_id") def test_activate_signal_success_variant(self): - """ActivateSignalResponse1 (success) should validate with required fields.""" - success = ActivateSignalResponse1( + """ActivateSignalSuccessResponse should validate with required fields.""" + success = ActivateSignalSuccessResponse( deployments=[], ) assert success.deployments == [] assert not hasattr(success, "errors") def test_activate_signal_error_variant(self): - """ActivateSignalResponse2 (error) should validate with errors field.""" - error = ActivateSignalResponse2( + """ActivateSignalErrorResponse should validate with errors field.""" + error = ActivateSignalErrorResponse( errors=[{"code": "unauthorized", "message": "Not authorized"}], ) assert len(error.errors) == 1 @@ -223,23 +229,23 @@ class TestUnionTypeValidation: """Test union type validation and deserialization.""" def test_success_response_from_dict(self): - """CreateMediaBuyResponse1 should validate success from dict.""" + """CreateMediaBuySuccessResponse should validate success from dict.""" data = { "media_buy_id": "mb_123", "buyer_ref": "ref_456", "packages": [], } - response = CreateMediaBuyResponse1.model_validate(data) - assert isinstance(response, CreateMediaBuyResponse1) + response = CreateMediaBuySuccessResponse.model_validate(data) + assert isinstance(response, CreateMediaBuySuccessResponse) assert response.media_buy_id == "mb_123" def test_error_response_from_dict(self): - """CreateMediaBuyResponse2 should validate error from dict.""" + """CreateMediaBuyErrorResponse should validate error from dict.""" data = { "errors": [{"code": "invalid", "message": "Invalid request"}], } - response = CreateMediaBuyResponse2.model_validate(data) - assert isinstance(response, CreateMediaBuyResponse2) + response = CreateMediaBuyErrorResponse.model_validate(data) + assert isinstance(response, CreateMediaBuyErrorResponse) assert len(response.errors) == 1 def test_platform_destination_from_dict(self): @@ -265,24 +271,24 @@ class TestSerializationRoundtrips: """Test that discriminated unions serialize and deserialize correctly.""" def test_success_response_roundtrip(self): - """CreateMediaBuyResponse1 should roundtrip through JSON.""" - original = CreateMediaBuyResponse1( + """CreateMediaBuySuccessResponse should roundtrip through JSON.""" + original = CreateMediaBuySuccessResponse( media_buy_id="mb_123", buyer_ref="ref_456", packages=[], ) json_str = original.model_dump_json() - parsed = CreateMediaBuyResponse1.model_validate_json(json_str) + parsed = CreateMediaBuySuccessResponse.model_validate_json(json_str) assert parsed.media_buy_id == original.media_buy_id assert parsed.buyer_ref == original.buyer_ref def test_error_response_roundtrip(self): - """CreateMediaBuyResponse2 should roundtrip through JSON.""" - original = CreateMediaBuyResponse2( + """CreateMediaBuyErrorResponse should roundtrip through JSON.""" + original = CreateMediaBuyErrorResponse( errors=[{"code": "invalid", "message": "Invalid"}], ) json_str = original.model_dump_json() - parsed = CreateMediaBuyResponse2.model_validate_json(json_str) + parsed = CreateMediaBuyErrorResponse.model_validate_json(json_str) assert len(parsed.errors) == len(original.errors) assert parsed.errors[0].code == original.errors[0].code diff --git a/tests/test_type_aliases.py b/tests/test_type_aliases.py new file mode 100644 index 0000000..e7394ef --- /dev/null +++ b/tests/test_type_aliases.py @@ -0,0 +1,179 @@ +"""Tests for semantic type aliases. + +Validates that: +1. All aliases import successfully +2. Aliases point to the correct generated types +3. Aliases can be used for type checking +""" + +from __future__ import annotations + +# Test that all aliases can be imported from the main package +from adcp import ( + ActivateSignalErrorResponse, + ActivateSignalSuccessResponse, + BuildCreativeErrorResponse, + BuildCreativeSuccessResponse, + CreateMediaBuyErrorResponse, + CreateMediaBuySuccessResponse, +) + +# Test that aliases can also be imported from the aliases module +from adcp.types.aliases import ( + ActivateSignalErrorResponse as AliasActivateSignalErrorResponse, +) +from adcp.types.aliases import ( + ActivateSignalSuccessResponse as AliasActivateSignalSuccessResponse, +) +from adcp.types.aliases import ( + BuildCreativeErrorResponse as AliasBuildCreativeErrorResponse, +) +from adcp.types.aliases import ( + BuildCreativeSuccessResponse as AliasBuildCreativeSuccessResponse, +) +from adcp.types.aliases import ( + CreateMediaBuyErrorResponse as AliasCreateMediaBuyErrorResponse, +) +from adcp.types.aliases import ( + CreateMediaBuySuccessResponse as AliasCreateMediaBuySuccessResponse, +) + +# Test that generated types still exist +from adcp.types.generated import ( + ActivateSignalResponse1, + ActivateSignalResponse2, + BuildCreativeResponse1, + BuildCreativeResponse2, + CreateMediaBuyResponse1, + CreateMediaBuyResponse2, +) + + +def test_aliases_import(): + """Test that all aliases can be imported without errors.""" + # If we got here, the imports succeeded + assert True + + +def test_aliases_point_to_correct_types(): + """Test that aliases point to the correct generated types.""" + # Response aliases + assert ActivateSignalSuccessResponse is ActivateSignalResponse1 + assert ActivateSignalErrorResponse is ActivateSignalResponse2 + assert BuildCreativeSuccessResponse is BuildCreativeResponse1 + assert BuildCreativeErrorResponse is BuildCreativeResponse2 + assert CreateMediaBuySuccessResponse is CreateMediaBuyResponse1 + assert CreateMediaBuyErrorResponse is CreateMediaBuyResponse2 + + +def test_aliases_from_main_module_match_aliases_module(): + """Test that aliases from main module match those from aliases module.""" + assert ActivateSignalSuccessResponse is AliasActivateSignalSuccessResponse + assert ActivateSignalErrorResponse is AliasActivateSignalErrorResponse + assert BuildCreativeSuccessResponse is AliasBuildCreativeSuccessResponse + assert BuildCreativeErrorResponse is AliasBuildCreativeErrorResponse + assert CreateMediaBuySuccessResponse is AliasCreateMediaBuySuccessResponse + assert CreateMediaBuyErrorResponse is AliasCreateMediaBuyErrorResponse + + +def test_aliases_have_docstrings(): + """Test that aliases module has helpful docstrings. + + Note: Type aliases don't preserve docstrings in Python, so we check + that the module itself has documentation explaining the aliases. + """ + import adcp.types.aliases as aliases_module + + # Module should have documentation + assert aliases_module.__doc__ is not None + assert "semantic" in aliases_module.__doc__.lower() + assert "alias" in aliases_module.__doc__.lower() + + +def test_semantic_names_are_meaningful(): + """Test that semantic names convey more meaning than generated names.""" + # The semantic name should be more descriptive + semantic_name = "CreateMediaBuySuccessResponse" + generated_name = "CreateMediaBuyResponse1" + + # Semantic names include "Success" or "Error" to indicate the outcome + assert "Success" in semantic_name or "Error" in semantic_name + # Generated names just have numbers + assert generated_name.endswith("1") or generated_name.endswith("2") + + +def test_all_response_aliases_exported(): + """Test that all expected response type aliases are exported.""" + expected_aliases = [ + # Activate signal + "ActivateSignalSuccessResponse", + "ActivateSignalErrorResponse", + # Build creative + "BuildCreativeSuccessResponse", + "BuildCreativeErrorResponse", + # Create media buy + "CreateMediaBuySuccessResponse", + "CreateMediaBuyErrorResponse", + # Performance feedback + "ProvidePerformanceFeedbackSuccessResponse", + "ProvidePerformanceFeedbackErrorResponse", + # Sync creatives + "SyncCreativesSuccessResponse", + "SyncCreativesErrorResponse", + # Update media buy + "UpdateMediaBuySuccessResponse", + "UpdateMediaBuyErrorResponse", + ] + + 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_all_request_aliases_exported(): + """Test that all expected request type aliases are exported.""" + expected_aliases = [ + "PreviewCreativeFormatRequest", + "PreviewCreativeManifestRequest", + "UpdateMediaBuyPackagesRequest", + "UpdateMediaBuyPropertiesRequest", + ] + + 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_all_activation_key_aliases_exported(): + """Test that all activation key aliases are exported.""" + expected_aliases = [ + "PropertyIdActivationKey", + "PropertyTagActivationKey", + ] + + 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_all_preview_render_aliases_exported(): + """Test that all preview render aliases are exported.""" + expected_aliases = [ + "PreviewRenderImage", + "PreviewRenderHtml", + "PreviewRenderIframe", + "PreviewCreativeStaticResponse", + "PreviewCreativeInteractiveResponse", + ] + + 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}"