diff --git a/CLAUDE.md b/CLAUDE.md index 7b44bb42..d76db824 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -134,6 +134,21 @@ from adcp.types.generated_poc.format import Asset as FormatAsset - Sharing common types via `$ref` - Using discriminated unions where appropriate +**PropertyTag/PropertyId Shared Schemas (Fixed Upstream in v1.0.0):** + +As of AdCP v1.0.0, the upstream schemas were refactored to use shared schema files: +- `property-id.json` - Single canonical definition of PropertyId +- `property-tag.json` - Single canonical definition of PropertyTag + +Both `adagents.json` and `publisher-property-selector.json` now reference these shared schemas instead of defining their own versions. This eliminates the previous type collision where both files defined identical but separate PropertyTag types. + +The code generator now produces: +- `src/adcp/types/generated_poc/property_id.py` - Generated from property-id.json +- `src/adcp/types/generated_poc/property_tag.py` - Generated from property-tag.json +- Both `adagents.py` and `publisher_property_selector.py` import from these shared modules + +**No SDK workaround needed** - the type system now correctly has a single PropertyTag definition. + **Current fixes applied:** 1. **Self-referential types** - Fixes `preview_render.py` if it contains module-qualified self-references diff --git a/schemas/cache/.hashes.json b/schemas/cache/.hashes.json index 3dfa431f..f167a6a8 100644 --- a/schemas/cache/.hashes.json +++ b/schemas/cache/.hashes.json @@ -1,6 +1,6 @@ { - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/index.json": "e07ecdba0bc0d4fba3fe5f8d7ce39cb60330dcb4b76a8717f14d606a76911ca6", - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/adagents.json": "f32e3778a454b7ae65f42952906f06466008bfd1208b83729e14d5f2164902d5", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/index.json": "1937a679dee895a367e2bb904b089ecc86e7e794e1c478cac3b5075551d5882f", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/adagents.json": "d7d23a4ca339c2c8dcbefccfbe29fc3c7a58b6f8eed7ca2efcaf8d617fda37ef", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/activation-key.json": "bb9c20c6200b651ce6db89f7160be60b9845dbbb4390a13363ea5b82c1c3c786", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/assets/audio-asset.json": "e25d688d22c8b130d9d1a2a09f9aa462cb7a5c83db6eea6f328cd937f8625a3f", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/assets/css-asset.json": "e0a3e3b668f564cf804d0b893833188203b8992cd5d118e4fd3d19c85cb501a1", @@ -38,9 +38,11 @@ "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/product.json": "e1b4faa9029bd06baa537fbf534993e7830b1bdc2241279dea4806c134cea50d", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/promoted-offerings.json": "d8b4b92db0e2debc5c0ddbc0a8ff673f258f0bbc0348737df61be20a25827077", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/promoted-products.json": "77773b1dce91b219ec5043c091eb2977a82ba301e03aead3868ba704e625379e", - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/property.json": "770734efdb4e0867c2367d5f12994fe53193f51bd8d03cb35bbb7b9b8b3d0a74", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/property-id.json": "46b21c016d58444aee5b5dae3e3a1e882cde274cf2c25f18e793ccace67617ea", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/property-tag.json": "798c575b3676823cc202dd69c5c31ec70954a6e7cc51b19f014c5f29153408ed", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/property.json": "e8374ba6180d16364e066aab85a429df4251be167e34fd3bccf2cf9f08ac1b41", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/protocol-envelope.json": "c6096b4ed4330c5e2045989bfd5cdc64fa6587cf8b0d1d2c19e33c7434cdacb8", - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/publisher-property-selector.json": "3e4870d0446a5825c16365a99d49932517223c1d9d3d46a4fbf413d377ed43dd", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/publisher-property-selector.json": "53da1f4894c73aa8f823489b10c9f66d75251bb4c0f2421db16518f89f88a1c7", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/push-notification-config.json": "d712ce49b101b83535dc7effcdeeb64ed2c07c67bcd9aa42ef3ffd450bf7112a", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/reporting-capabilities.json": "c84fa2a1ecc71181aa42b71e2960742ea05d3f4113c9f7a2269c0b0d7ab618c4", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/response.json": "0ac624a30da08e1aa90d2a9379f8c1ed29b704c3f5399224b9684672d3df9723", diff --git a/schemas/cache/1.0.0/adagents.json b/schemas/cache/1.0.0/adagents.json index c50e6a85..32e33696 100644 --- a/schemas/cache/1.0.0/adagents.json +++ b/schemas/cache/1.0.0/adagents.json @@ -244,8 +244,7 @@ "property_ids": { "description": "Property IDs this agent is authorized for. Resolved against the top-level properties array in this file", "items": { - "pattern": "^[a-z0-9_]+$", - "type": "string" + "$ref": "/schemas/v1/core/property-id.json" }, "minItems": 1, "type": "array" @@ -281,8 +280,7 @@ "property_tags": { "description": "Tags identifying which properties this agent is authorized for. Resolved against the top-level properties array in this file using tag matching", "items": { - "pattern": "^[a-z0-9_]+$", - "type": "string" + "$ref": "/schemas/v1/core/property-tag.json" }, "minItems": 1, "type": "array" diff --git a/schemas/cache/1.0.0/index.json b/schemas/cache/1.0.0/index.json index 6c0256b4..b44e1718 100644 --- a/schemas/cache/1.0.0/index.json +++ b/schemas/cache/1.0.0/index.json @@ -146,6 +146,14 @@ "$ref": "/schemas/v1/core/property.json", "description": "An advertising property that can be validated via adagents.json" }, + "property-id": { + "$ref": "/schemas/v1/core/property-id.json", + "description": "Identifier for a publisher property - lowercase alphanumeric with underscores only" + }, + "property-tag": { + "$ref": "/schemas/v1/core/property-tag.json", + "description": "Tag for categorizing publisher properties - lowercase alphanumeric with underscores only" + }, "protocol-envelope": { "$ref": "/schemas/v1/core/protocol-envelope.json", "description": "Standard envelope structure added by protocol layer (MCP, A2A, REST) that wraps task response payloads with protocol-level fields like status, context_id, task_id, and message" diff --git a/schemas/cache/1.0.0/property-id.json b/schemas/cache/1.0.0/property-id.json new file mode 100644 index 00000000..307304a3 --- /dev/null +++ b/schemas/cache/1.0.0/property-id.json @@ -0,0 +1,14 @@ +{ + "$id": "/schemas/v1/core/property-id.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.", + "examples": [ + "cnn_ctv_app", + "homepage", + "mobile_ios", + "instagram" + ], + "pattern": "^[a-z0-9_]+$", + "title": "Property ID", + "type": "string" +} \ No newline at end of file diff --git a/schemas/cache/1.0.0/property-tag.json b/schemas/cache/1.0.0/property-tag.json new file mode 100644 index 00000000..354bcfbc --- /dev/null +++ b/schemas/cache/1.0.0/property-tag.json @@ -0,0 +1,16 @@ +{ + "$id": "/schemas/v1/core/property-tag.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.", + "examples": [ + "ctv", + "premium", + "news", + "sports", + "meta_network", + "social_media" + ], + "pattern": "^[a-z0-9_]+$", + "title": "Property Tag", + "type": "string" +} \ No newline at end of file diff --git a/schemas/cache/1.0.0/property.json b/schemas/cache/1.0.0/property.json index f57739df..1dc7b625 100644 --- a/schemas/cache/1.0.0/property.json +++ b/schemas/cache/1.0.0/property.json @@ -32,9 +32,8 @@ "type": "string" }, "property_id": { - "description": "Unique identifier for this property (optional). Enables referencing properties by ID instead of repeating full objects. Recommended format: lowercase with underscores (e.g., 'cnn_ctv_app', 'instagram_mobile')", - "pattern": "^[a-z0-9_]+$", - "type": "string" + "$ref": "/schemas/v1/core/property-id.json", + "description": "Unique identifier for this property (optional). Enables referencing properties by ID instead of repeating full objects." }, "property_type": { "$ref": "/schemas/v1/enums/property-type.json", @@ -47,9 +46,7 @@ "tags": { "description": "Tags for categorization and grouping (e.g., network membership, content categories)", "items": { - "description": "Lowercase tag with underscores (e.g., 'conde_nast_network', 'premium_content')", - "pattern": "^[a-z0-9_]+$", - "type": "string" + "$ref": "/schemas/v1/core/property-tag.json" }, "type": "array", "uniqueItems": true diff --git a/schemas/cache/1.0.0/publisher-property-selector.json b/schemas/cache/1.0.0/publisher-property-selector.json index 228e6b8d..9af60f05 100644 --- a/schemas/cache/1.0.0/publisher-property-selector.json +++ b/schemas/cache/1.0.0/publisher-property-selector.json @@ -34,8 +34,7 @@ "property_ids": { "description": "Specific property IDs from the publisher's adagents.json", "items": { - "pattern": "^[a-z0-9_]+$", - "type": "string" + "$ref": "/schemas/v1/core/property-id.json" }, "minItems": 1, "type": "array" @@ -65,8 +64,7 @@ "property_tags": { "description": "Property tags from the publisher's adagents.json. Selector covers all properties with these tags", "items": { - "pattern": "^[a-z0-9_]+$", - "type": "string" + "$ref": "/schemas/v1/core/property-tag.json" }, "minItems": 1, "type": "array" diff --git a/src/adcp/types/__init__.py b/src/adcp/types/__init__.py index 8ad8f4c9..fd6d64f8 100644 --- a/src/adcp/types/__init__.py +++ b/src/adcp/types/__init__.py @@ -182,7 +182,6 @@ StatusSummary, SyncCreativesRequest, SyncCreativesResponse, - Tag, Tags, TargetingOverlay, Task, @@ -426,7 +425,6 @@ "Results", "Signal", "SignalFilters", - "Tag", "Tags", "TargetingOverlay", "VenueBreakdownItem", diff --git a/src/adcp/types/_generated.py b/src/adcp/types/_generated.py index 8b425466..75382f1e 100644 --- a/src/adcp/types/_generated.py +++ b/src/adcp/types/_generated.py @@ -10,7 +10,7 @@ DO NOT EDIT MANUALLY. Generated from: https://github.com/adcontextprotocol/adcp/tree/main/schemas -Generation date: 2025-11-21 15:57:17 UTC +Generation date: 2025-11-21 20:45:16 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations @@ -19,7 +19,7 @@ from adcp.types.generated_poc.activate_signal_request import ActivateSignalRequest from adcp.types.generated_poc.activate_signal_response import ActivateSignalResponse, ActivateSignalResponse1, ActivateSignalResponse2 from adcp.types.generated_poc.activation_key import ActivationKey1, ActivationKey2 -from adcp.types.generated_poc.adagents import AuthorizedAgents, AuthorizedAgents1, AuthorizedAgents2, AuthorizedAgents3, AuthorizedSalesAgents, Contact, PropertyId, PropertyTag, Tags +from adcp.types.generated_poc.adagents import AuthorizedAgents, AuthorizedAgents1, AuthorizedAgents2, AuthorizedAgents3, AuthorizedSalesAgents, Contact, Tags from adcp.types.generated_poc.adcp_domain import AdcpDomain from adcp.types.generated_poc.asset_content_type import AssetContentType from adcp.types.generated_poc.audio_asset import AudioAsset @@ -104,7 +104,9 @@ from adcp.types.generated_poc.product_filters import ProductFilters from adcp.types.generated_poc.promoted_offerings import AssetSelectors, AssetType, Offering, PromotedOfferings from adcp.types.generated_poc.promoted_products import PromotedProducts -from adcp.types.generated_poc.property import Identifier, Property, Tag +from adcp.types.generated_poc.property import Identifier, Property +from adcp.types.generated_poc.property_id import PropertyId +from adcp.types.generated_poc.property_tag import PropertyTag from adcp.types.generated_poc.property_type import PropertyType from adcp.types.generated_poc.protocol_envelope import ProtocolEnvelope from adcp.types.generated_poc.provide_performance_feedback_request import ProvidePerformanceFeedbackRequest, ProvidePerformanceFeedbackRequest1, ProvidePerformanceFeedbackRequest2 @@ -202,7 +204,7 @@ "SignalCatalogType", "SignalFilters", "Sort", "SortApplied", "SortDirection", "StandardFormatIds", "Status", "StatusSummary", "SubAsset1", "SubAsset2", "SyncCreativesRequest", "SyncCreativesResponse", "SyncCreativesResponse1", - "SyncCreativesResponse2", "Tag", "Tags", "TargetingOverlay", "Task", "TaskStatus", "TaskType", + "SyncCreativesResponse2", "Tags", "TargetingOverlay", "Task", "TaskStatus", "TaskType", "TasksGetRequest", "TasksGetResponse", "TasksListRequest", "TasksListResponse", "TextAsset", "Totals", "Type", "UpdateFrequency", "UpdateMediaBuyRequest", "UpdateMediaBuyRequest1", "UpdateMediaBuyRequest2", "UpdateMediaBuyResponse", "UpdateMediaBuyResponse1", diff --git a/src/adcp/types/generated_poc/adagents.py b/src/adcp/types/generated_poc/adagents.py index 6833985b..0f079175 100644 --- a/src/adcp/types/generated_poc/adagents.py +++ b/src/adcp/types/generated_poc/adagents.py @@ -1,75 +1,15 @@ # generated by datamodel-codegen: # filename: adagents.json -# timestamp: 2025-11-19T02:02:39+00:00 +# timestamp: 2025-11-21T20:45:16+00:00 from __future__ import annotations from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field -from . import property, publisher_property_selector - - -class PropertyId(RootModel[str]): - root: Annotated[str, Field(pattern='^[a-z0-9_]+$')] - - -class AuthorizedAgents(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - authorization_type: Annotated[ - Literal['property_ids'], - Field(description='Discriminator indicating authorization by specific property IDs'), - ] - authorized_for: Annotated[ - str, - Field( - description='Human-readable description of what this agent is authorized to sell', - max_length=500, - min_length=1, - ), - ] - property_ids: Annotated[ - list[PropertyId], - Field( - description='Property IDs this agent is authorized for. Resolved against the top-level properties array in this file', - min_length=1, - ), - ] - url: Annotated[AnyUrl, Field(description="The authorized agent's API endpoint URL")] - - -class PropertyTag(PropertyId): - pass - - -class AuthorizedAgents1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - authorization_type: Annotated[ - Literal['property_tags'], - Field(description='Discriminator indicating authorization by property tags'), - ] - authorized_for: Annotated[ - str, - Field( - description='Human-readable description of what this agent is authorized to sell', - max_length=500, - min_length=1, - ), - ] - property_tags: Annotated[ - list[PropertyTag], - Field( - description='Tags identifying which properties this agent is authorized for. Resolved against the top-level properties array in this file using tag matching', - min_length=1, - ), - ] - url: Annotated[AnyUrl, Field(description="The authorized agent's API endpoint URL")] +from . import property, property_id, property_tag, publisher_property_selector class Contact(AdCPBaseModel): @@ -125,6 +65,58 @@ class Tags(AdCPBaseModel): name: Annotated[str, Field(description='Human-readable name for this tag')] +class AuthorizedAgents(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + authorization_type: Annotated[ + Literal['property_ids'], + Field(description='Discriminator indicating authorization by specific property IDs'), + ] + authorized_for: Annotated[ + str, + Field( + description='Human-readable description of what this agent is authorized to sell', + max_length=500, + min_length=1, + ), + ] + property_ids: Annotated[ + list[property_id.PropertyId], + Field( + description='Property IDs this agent is authorized for. Resolved against the top-level properties array in this file', + min_length=1, + ), + ] + url: Annotated[AnyUrl, Field(description="The authorized agent's API endpoint URL")] + + +class AuthorizedAgents1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + authorization_type: Annotated[ + Literal['property_tags'], + Field(description='Discriminator indicating authorization by property tags'), + ] + authorized_for: Annotated[ + str, + Field( + description='Human-readable description of what this agent is authorized to sell', + max_length=500, + min_length=1, + ), + ] + property_tags: Annotated[ + list[property_tag.PropertyTag], + Field( + description='Tags identifying which properties this agent is authorized for. Resolved against the top-level properties array in this file using tag matching', + min_length=1, + ), + ] + url: Annotated[AnyUrl, Field(description="The authorized agent's API endpoint URL")] + + class AuthorizedAgents2(AdCPBaseModel): model_config = ConfigDict( extra='forbid', diff --git a/src/adcp/types/generated_poc/property.py b/src/adcp/types/generated_poc/property.py index 99d68e2b..f28c3863 100644 --- a/src/adcp/types/generated_poc/property.py +++ b/src/adcp/types/generated_poc/property.py @@ -1,15 +1,15 @@ # generated by datamodel-codegen: # filename: property.json -# timestamp: 2025-11-21T12:49:05+00:00 +# timestamp: 2025-11-21T20:45:16+00:00 from __future__ import annotations from typing import Annotated from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field -from . import identifier_types +from . import identifier_types, property_tag from . import property_type as property_type_1 @@ -29,16 +29,6 @@ class Identifier(AdCPBaseModel): ] -class Tag(RootModel[str]): - root: Annotated[ - str, - Field( - description="Lowercase tag with underscores (e.g., 'conde_nast_network', 'premium_content')", - pattern='^[a-z0-9_]+$', - ), - ] - - class Property(AdCPBaseModel): model_config = ConfigDict( extra='forbid', @@ -50,8 +40,10 @@ class Property(AdCPBaseModel): property_id: Annotated[ str | None, Field( - description="Unique identifier for this property (optional). Enables referencing properties by ID instead of repeating full objects. Recommended format: lowercase with underscores (e.g., 'cnn_ctv_app', 'instagram_mobile')", + description='Unique identifier for this property (optional). Enables referencing properties by ID instead of repeating full objects.', + examples=['cnn_ctv_app', 'homepage', 'mobile_ios', 'instagram'], pattern='^[a-z0-9_]+$', + title='Property ID', ), ] = None property_type: Annotated[ @@ -64,7 +56,7 @@ class Property(AdCPBaseModel): ), ] = None tags: Annotated[ - list[Tag] | None, + list[property_tag.PropertyTag] | None, Field( description='Tags for categorization and grouping (e.g., network membership, content categories)' ), diff --git a/src/adcp/types/generated_poc/property_id.py b/src/adcp/types/generated_poc/property_id.py new file mode 100644 index 00000000..a77be600 --- /dev/null +++ b/src/adcp/types/generated_poc/property_id.py @@ -0,0 +1,21 @@ +# generated by datamodel-codegen: +# filename: property-id.json +# timestamp: 2025-11-21T20:45:16+00:00 + +from __future__ import annotations + +from typing import Annotated + +from pydantic import Field, RootModel + + +class PropertyId(RootModel[str]): + root: Annotated[ + str, + Field( + description='Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.', + examples=['cnn_ctv_app', 'homepage', 'mobile_ios', 'instagram'], + pattern='^[a-z0-9_]+$', + title='Property ID', + ), + ] diff --git a/src/adcp/types/generated_poc/property_tag.py b/src/adcp/types/generated_poc/property_tag.py new file mode 100644 index 00000000..7ea2d678 --- /dev/null +++ b/src/adcp/types/generated_poc/property_tag.py @@ -0,0 +1,21 @@ +# generated by datamodel-codegen: +# filename: property-tag.json +# timestamp: 2025-11-21T20:45:16+00:00 + +from __future__ import annotations + +from typing import Annotated + +from pydantic import Field, RootModel + + +class PropertyTag(RootModel[str]): + root: Annotated[ + str, + Field( + description='Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.', + examples=['ctv', 'premium', 'news', 'sports', 'meta_network', 'social_media'], + pattern='^[a-z0-9_]+$', + title='Property Tag', + ), + ] diff --git a/src/adcp/types/generated_poc/publisher_property_selector.py b/src/adcp/types/generated_poc/publisher_property_selector.py index 798d964f..b13f0cdd 100644 --- a/src/adcp/types/generated_poc/publisher_property_selector.py +++ b/src/adcp/types/generated_poc/publisher_property_selector.py @@ -1,13 +1,15 @@ # generated by datamodel-codegen: # filename: publisher-property-selector.json -# timestamp: 2025-11-19T02:02:39+00:00 +# timestamp: 2025-11-21T20:45:16+00:00 from __future__ import annotations from typing import Annotated, Literal from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field + +from . import property_id, property_tag class PublisherPropertySelector1(AdCPBaseModel): @@ -29,16 +31,12 @@ class PublisherPropertySelector1(AdCPBaseModel): ] -class PropertyId(RootModel[str]): - root: Annotated[str, Field(pattern='^[a-z0-9_]+$')] - - class PublisherPropertySelector2(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) property_ids: Annotated[ - list[PropertyId], + list[property_id.PropertyId], Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), ] publisher_domain: Annotated[ @@ -54,16 +52,12 @@ class PublisherPropertySelector2(AdCPBaseModel): ] -class PropertyTag(PropertyId): - pass - - class PublisherPropertySelector3(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) property_tags: Annotated[ - list[PropertyTag], + list[property_tag.PropertyTag], Field( description="Property tags from the publisher's adagents.json. Selector covers all properties with these tags", min_length=1, diff --git a/tests/test_discriminated_unions.py b/tests/test_discriminated_unions.py index 834cc6fd..1bf4a815 100644 --- a/tests/test_discriminated_unions.py +++ b/tests/test_discriminated_unions.py @@ -769,3 +769,78 @@ def test_text_sub_asset_roundtrip(self): parsed = TextSubAsset.model_validate_json(json_str) assert parsed.asset_kind == "text" assert parsed.content == original.content + + +class TestPropertyTagSharedSchema: + """Test that PropertyTag uses shared schema definition. + + As of AdCP v1.0.0, PropertyTag and PropertyId are defined in separate shared + schema files (property-tag.json and property-id.json) that are referenced by + both adagents.json and publisher-property-selector.json. + + This eliminates the previous name collision where both files defined their own + identical PropertyTag types. + """ + + def test_public_property_tag_is_from_shared_schema(self): + """Public PropertyTag should be from the shared property_tag schema.""" + from adcp import PropertyTag + + # Should come from the shared schema, not embedded in another schema + assert PropertyTag.__module__ == "adcp.types.generated_poc.property_tag" + + def test_property_id_is_from_shared_schema(self): + """Public PropertyId should be from the shared property_id schema.""" + from adcp import PropertyId + + # Should come from the shared schema + assert PropertyId.__module__ == "adcp.types.generated_poc.property_id" + + def test_property_tag_works_with_publisher_properties_by_tag(self): + """PropertyTag should work correctly with PublisherPropertiesByTag.""" + from adcp import PropertyTag, PublisherPropertiesByTag + + props = PublisherPropertiesByTag( + publisher_domain="example.com", + selection_type="by_tag", + property_tags=[PropertyTag("premium"), PropertyTag("video")] + ) + + assert props.selection_type == "by_tag" + assert len(props.property_tags) == 2 + assert str(props.property_tags[0].root) == "premium" + assert str(props.property_tags[1].root) == "video" + + def test_shared_schema_prevents_collision(self): + """Verify that both adagents and publisher_property_selector import from shared schema.""" + from adcp.types.generated_poc import property_tag + from adcp import PropertyTag + + # The shared schema is the canonical definition + assert PropertyTag is property_tag.PropertyTag + + # Both adagents.py and publisher_property_selector.py should import from property_tag module + # (not define their own) + import inspect + import adcp.types.generated_poc.adagents as adagents_module + import adcp.types.generated_poc.publisher_property_selector as selector_module + + # Check that they import from property_tag, not define their own + adagents_source = inspect.getsource(adagents_module) + selector_source = inspect.getsource(selector_module) + + # May be on same line as other imports or separate line + assert "property_tag" in adagents_source and "from . import" in adagents_source + assert "property_tag" in selector_source and "from . import" in selector_source + + def test_property_tag_validation(self): + """PropertyTag should validate according to shared schema rules.""" + from adcp import PropertyTag + + # Valid tags: lowercase alphanumeric + underscores + valid = PropertyTag("premium_video") + assert str(valid.root) == "premium_video" + + # Pattern validation should work + with pytest.raises(ValidationError): + PropertyTag("Invalid-Tag") # Hyphens not allowed in property tags diff --git a/tests/test_type_aliases.py b/tests/test_type_aliases.py index fd19e072..a5ee2e4d 100644 --- a/tests/test_type_aliases.py +++ b/tests/test_type_aliases.py @@ -430,8 +430,14 @@ def test_publisher_properties_aliases_in_exports(): def test_property_id_and_tag_are_root_models(): - """Test that PropertyId and PropertyTag are properly constrained string types.""" + """Test that PropertyId and PropertyTag are properly constrained string types. + + As of AdCP v1.0.0, PropertyId and PropertyTag are separate RootModel types + defined in their own schema files, not in an inheritance relationship. + Both use the same pattern constraint (^[a-z0-9_]+$) but are semantically distinct. + """ from adcp import PropertyId, PropertyTag + from pydantic import RootModel # Create valid PropertyId and PropertyTag prop_id = PropertyId("my_property_id") @@ -441,8 +447,14 @@ def test_property_id_and_tag_are_root_models(): assert prop_id.root == "my_property_id" assert prop_tag.root == "premium" - # PropertyTag should be a subclass of PropertyId - assert issubclass(PropertyTag, PropertyId) + # Both should be RootModel subclasses (but not related to each other) + assert issubclass(PropertyId, RootModel) + assert issubclass(PropertyTag, RootModel) + + # They are separate types, not in an inheritance relationship + assert PropertyId is not PropertyTag + assert not issubclass(PropertyTag, PropertyId) + assert not issubclass(PropertyId, PropertyTag) def test_deployment_aliases_imports():