adcp 2.17.0__py3-none-any.whl → 2.19.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. adcp/ADCP_VERSION +1 -1
  2. adcp/__init__.py +22 -1
  3. adcp/__main__.py +72 -0
  4. adcp/protocols/mcp.py +3 -1
  5. adcp/types/_ergonomic.py +0 -4
  6. adcp/types/_generated.py +91 -4
  7. adcp/types/generated_poc/adagents.py +239 -149
  8. adcp/types/generated_poc/core/activation_key.py +9 -9
  9. adcp/types/generated_poc/core/assets/audio_asset.py +6 -6
  10. adcp/types/generated_poc/core/assets/css_asset.py +3 -3
  11. adcp/types/generated_poc/core/assets/daast_asset.py +19 -19
  12. adcp/types/generated_poc/core/assets/html_asset.py +3 -3
  13. adcp/types/generated_poc/core/assets/image_asset.py +7 -7
  14. adcp/types/generated_poc/core/assets/javascript_asset.py +4 -4
  15. adcp/types/generated_poc/core/assets/text_asset.py +3 -3
  16. adcp/types/generated_poc/core/assets/url_asset.py +4 -4
  17. adcp/types/generated_poc/core/assets/vast_asset.py +19 -19
  18. adcp/types/generated_poc/core/assets/video_asset.py +8 -8
  19. adcp/types/generated_poc/core/assets/webhook_asset.py +10 -10
  20. adcp/types/generated_poc/core/async_response_data.py +2 -2
  21. adcp/types/generated_poc/core/brand_manifest.py +56 -56
  22. adcp/types/generated_poc/core/brand_manifest_ref.py +9 -9
  23. adcp/types/generated_poc/core/context.py +2 -3
  24. adcp/types/generated_poc/core/creative_asset.py +14 -14
  25. adcp/types/generated_poc/core/creative_assignment.py +4 -4
  26. adcp/types/generated_poc/core/creative_filters.py +20 -20
  27. adcp/types/generated_poc/core/creative_manifest.py +3 -3
  28. adcp/types/generated_poc/core/creative_policy.py +5 -5
  29. adcp/types/generated_poc/core/delivery_metrics.py +33 -33
  30. adcp/types/generated_poc/core/deployment.py +21 -21
  31. adcp/types/generated_poc/core/destination.py +12 -12
  32. adcp/types/generated_poc/core/error.py +9 -9
  33. adcp/types/generated_poc/core/ext.py +2 -3
  34. adcp/types/generated_poc/core/format.py +139 -51
  35. adcp/types/generated_poc/core/format_id.py +6 -6
  36. adcp/types/generated_poc/core/frequency_cap.py +3 -3
  37. adcp/types/generated_poc/core/identifier.py +27 -0
  38. adcp/types/generated_poc/core/mcp_webhook_payload.py +10 -10
  39. adcp/types/generated_poc/core/measurement.py +9 -9
  40. adcp/types/generated_poc/core/media_buy.py +8 -8
  41. adcp/types/generated_poc/core/package.py +9 -9
  42. adcp/types/generated_poc/core/performance_feedback.py +19 -19
  43. adcp/types/generated_poc/core/placement.py +5 -5
  44. adcp/types/generated_poc/core/pricing_option.py +2 -2
  45. adcp/types/generated_poc/core/product.py +21 -21
  46. adcp/types/generated_poc/core/product_filters.py +19 -19
  47. adcp/types/generated_poc/core/promoted_offerings.py +21 -21
  48. adcp/types/generated_poc/core/promoted_products.py +3 -3
  49. adcp/types/generated_poc/core/property.py +10 -10
  50. adcp/types/generated_poc/core/property_id.py +4 -4
  51. adcp/types/generated_poc/core/property_list_ref.py +26 -0
  52. adcp/types/generated_poc/core/property_tag.py +4 -4
  53. adcp/types/generated_poc/core/protocol_envelope.py +9 -9
  54. adcp/types/generated_poc/core/publisher_property_selector.py +14 -14
  55. adcp/types/generated_poc/core/push_notification_config.py +5 -5
  56. adcp/types/generated_poc/core/reporting_capabilities.py +9 -9
  57. adcp/types/generated_poc/core/response.py +5 -5
  58. adcp/types/generated_poc/core/signal_filters.py +6 -6
  59. adcp/types/generated_poc/core/start_timing.py +5 -5
  60. adcp/types/generated_poc/core/sub_asset.py +15 -15
  61. adcp/types/generated_poc/core/targeting.py +9 -9
  62. adcp/types/generated_poc/creative/list_creative_formats_request.py +21 -21
  63. adcp/types/generated_poc/creative/list_creative_formats_response.py +6 -6
  64. adcp/types/generated_poc/creative/preview_creative_request.py +26 -26
  65. adcp/types/generated_poc/creative/preview_creative_response.py +31 -30
  66. adcp/types/generated_poc/creative/preview_render.py +26 -26
  67. adcp/types/generated_poc/enums/adcp_domain.py +5 -3
  68. adcp/types/generated_poc/enums/asset_content_type.py +13 -13
  69. adcp/types/generated_poc/enums/auth_scheme.py +2 -2
  70. adcp/types/generated_poc/enums/available_metric.py +9 -9
  71. adcp/types/generated_poc/enums/channels.py +9 -9
  72. adcp/types/generated_poc/enums/co_branding_requirement.py +3 -3
  73. adcp/types/generated_poc/enums/creative_action.py +5 -5
  74. adcp/types/generated_poc/enums/creative_agent_capability.py +4 -4
  75. adcp/types/generated_poc/enums/creative_sort_field.py +6 -6
  76. adcp/types/generated_poc/enums/creative_status.py +4 -4
  77. adcp/types/generated_poc/enums/daast_tracking_event.py +11 -11
  78. adcp/types/generated_poc/enums/daast_version.py +2 -2
  79. adcp/types/generated_poc/enums/delivery_type.py +2 -2
  80. adcp/types/generated_poc/enums/dimension_unit.py +4 -4
  81. adcp/types/generated_poc/enums/feed_format.py +3 -3
  82. adcp/types/generated_poc/enums/feedback_source.py +4 -4
  83. adcp/types/generated_poc/enums/format_category.py +7 -7
  84. adcp/types/generated_poc/enums/format_id_parameter.py +2 -2
  85. adcp/types/generated_poc/enums/frequency_cap_scope.py +3 -3
  86. adcp/types/generated_poc/enums/history_entry_type.py +2 -2
  87. adcp/types/generated_poc/enums/http_method.py +2 -2
  88. adcp/types/generated_poc/enums/identifier_types.py +19 -19
  89. adcp/types/generated_poc/enums/javascript_module_type.py +3 -3
  90. adcp/types/generated_poc/enums/landing_page_requirement.py +3 -3
  91. adcp/types/generated_poc/enums/markdown_flavor.py +2 -2
  92. adcp/types/generated_poc/enums/media_buy_status.py +4 -4
  93. adcp/types/generated_poc/enums/metric_type.py +8 -8
  94. adcp/types/generated_poc/enums/notification_type.py +4 -4
  95. adcp/types/generated_poc/enums/pacing.py +3 -3
  96. adcp/types/generated_poc/enums/preview_output_format.py +2 -2
  97. adcp/types/generated_poc/enums/pricing_model.py +7 -7
  98. adcp/types/generated_poc/enums/property_type.py +7 -7
  99. adcp/types/generated_poc/enums/publisher_identifier_types.py +5 -5
  100. adcp/types/generated_poc/enums/reporting_frequency.py +3 -3
  101. adcp/types/generated_poc/enums/signal_catalog_type.py +3 -3
  102. adcp/types/generated_poc/enums/sort_direction.py +2 -2
  103. adcp/types/generated_poc/enums/standard_format_ids.py +35 -35
  104. adcp/types/generated_poc/enums/task_status.py +9 -9
  105. adcp/types/generated_poc/enums/task_type.py +12 -6
  106. adcp/types/generated_poc/enums/update_frequency.py +4 -4
  107. adcp/types/generated_poc/enums/url_asset_type.py +3 -3
  108. adcp/types/generated_poc/enums/validation_mode.py +2 -2
  109. adcp/types/generated_poc/enums/vast_tracking_event.py +16 -16
  110. adcp/types/generated_poc/enums/vast_version.py +5 -5
  111. adcp/types/generated_poc/enums/webhook_response_type.py +4 -4
  112. adcp/types/generated_poc/enums/webhook_security_method.py +3 -3
  113. adcp/types/generated_poc/extensions/__init__.py +3 -0
  114. adcp/types/generated_poc/extensions/extension_meta.py +50 -0
  115. adcp/types/generated_poc/media_buy/build_creative_request.py +5 -5
  116. adcp/types/generated_poc/media_buy/build_creative_response.py +7 -7
  117. adcp/types/generated_poc/media_buy/create_media_buy_async_response_input_required.py +6 -6
  118. adcp/types/generated_poc/media_buy/create_media_buy_async_response_submitted.py +2 -2
  119. adcp/types/generated_poc/media_buy/create_media_buy_async_response_working.py +6 -6
  120. adcp/types/generated_poc/media_buy/create_media_buy_request.py +64 -23
  121. adcp/types/generated_poc/media_buy/create_media_buy_response.py +8 -8
  122. adcp/types/generated_poc/media_buy/get_media_buy_delivery_request.py +9 -9
  123. adcp/types/generated_poc/media_buy/get_media_buy_delivery_response.py +52 -52
  124. adcp/types/generated_poc/media_buy/get_products_async_response_input_required.py +7 -7
  125. adcp/types/generated_poc/media_buy/get_products_async_response_submitted.py +3 -3
  126. adcp/types/generated_poc/media_buy/get_products_async_response_working.py +5 -5
  127. adcp/types/generated_poc/media_buy/get_products_request.py +11 -5
  128. adcp/types/generated_poc/media_buy/get_products_response.py +10 -4
  129. adcp/types/generated_poc/media_buy/list_authorized_properties_request.py +4 -4
  130. adcp/types/generated_poc/media_buy/list_authorized_properties_response.py +8 -8
  131. adcp/types/generated_poc/media_buy/list_creative_formats_request.py +10 -10
  132. adcp/types/generated_poc/media_buy/list_creative_formats_response.py +6 -6
  133. adcp/types/generated_poc/media_buy/list_creatives_request.py +24 -24
  134. adcp/types/generated_poc/media_buy/list_creatives_response.py +53 -53
  135. adcp/types/generated_poc/media_buy/package_request.py +16 -7
  136. adcp/types/generated_poc/media_buy/provide_performance_feedback_request.py +20 -20
  137. adcp/types/generated_poc/media_buy/provide_performance_feedback_response.py +7 -7
  138. adcp/types/generated_poc/media_buy/sync_creatives_async_response_input_required.py +6 -6
  139. adcp/types/generated_poc/media_buy/sync_creatives_async_response_submitted.py +2 -2
  140. adcp/types/generated_poc/media_buy/sync_creatives_async_response_working.py +8 -8
  141. adcp/types/generated_poc/media_buy/sync_creatives_request.py +8 -8
  142. adcp/types/generated_poc/media_buy/sync_creatives_response.py +16 -16
  143. adcp/types/generated_poc/media_buy/update_media_buy_async_response_input_required.py +5 -5
  144. adcp/types/generated_poc/media_buy/update_media_buy_async_response_submitted.py +2 -2
  145. adcp/types/generated_poc/media_buy/update_media_buy_async_response_working.py +6 -6
  146. adcp/types/generated_poc/media_buy/update_media_buy_request.py +37 -29
  147. adcp/types/generated_poc/media_buy/update_media_buy_response.py +8 -8
  148. adcp/types/generated_poc/pricing_options/cpc_option.py +9 -9
  149. adcp/types/generated_poc/pricing_options/cpcv_option.py +9 -9
  150. adcp/types/generated_poc/pricing_options/cpm_auction_option.py +14 -14
  151. adcp/types/generated_poc/pricing_options/cpm_fixed_option.py +9 -9
  152. adcp/types/generated_poc/pricing_options/cpp_option.py +14 -14
  153. adcp/types/generated_poc/pricing_options/cpv_option.py +13 -13
  154. adcp/types/generated_poc/pricing_options/flat_rate_option.py +16 -16
  155. adcp/types/generated_poc/pricing_options/vcpm_auction_option.py +14 -14
  156. adcp/types/generated_poc/pricing_options/vcpm_fixed_option.py +9 -9
  157. adcp/types/generated_poc/property/__init__.py +3 -0
  158. adcp/types/generated_poc/property/base_property_source.py +86 -0
  159. adcp/types/generated_poc/property/create_property_list_request.py +43 -0
  160. adcp/types/generated_poc/property/create_property_list_response.py +27 -0
  161. adcp/types/generated_poc/property/delete_property_list_request.py +22 -0
  162. adcp/types/generated_poc/property/delete_property_list_response.py +21 -0
  163. adcp/types/generated_poc/property/feature_requirement.py +42 -0
  164. adcp/types/generated_poc/property/get_property_list_request.py +34 -0
  165. adcp/types/generated_poc/property/get_property_list_response.py +61 -0
  166. adcp/types/generated_poc/property/list_property_features_request.py +25 -0
  167. adcp/types/generated_poc/property/list_property_features_response.py +24 -0
  168. adcp/types/generated_poc/property/list_property_lists_request.py +29 -0
  169. adcp/types/generated_poc/property/list_property_lists_response.py +39 -0
  170. adcp/types/generated_poc/property/property_error.py +33 -0
  171. adcp/types/generated_poc/property/property_feature.py +22 -0
  172. adcp/types/generated_poc/property/property_feature_definition.py +80 -0
  173. adcp/types/generated_poc/property/property_list.py +62 -0
  174. adcp/types/generated_poc/property/property_list_changed_webhook.py +51 -0
  175. adcp/types/generated_poc/property/property_list_filters.py +47 -0
  176. adcp/types/generated_poc/property/update_property_list_request.py +46 -0
  177. adcp/types/generated_poc/property/update_property_list_response.py +21 -0
  178. adcp/types/generated_poc/protocols/adcp_extension.py +26 -10
  179. adcp/types/generated_poc/signals/activate_signal_request.py +4 -4
  180. adcp/types/generated_poc/signals/activate_signal_response.py +7 -7
  181. adcp/types/generated_poc/signals/get_signals_request.py +9 -9
  182. adcp/types/generated_poc/signals/get_signals_response.py +16 -16
  183. adcp/utils/__init__.py +24 -1
  184. adcp/utils/format_assets.py +224 -0
  185. adcp/utils/preview_cache.py +29 -7
  186. {adcp-2.17.0.dist-info → adcp-2.19.0.dist-info}/METADATA +1 -1
  187. adcp-2.19.0.dist-info/RECORD +220 -0
  188. adcp-2.17.0.dist-info/RECORD +0 -194
  189. {adcp-2.17.0.dist-info → adcp-2.19.0.dist-info}/WHEEL +0 -0
  190. {adcp-2.17.0.dist-info → adcp-2.19.0.dist-info}/entry_points.txt +0 -0
  191. {adcp-2.17.0.dist-info → adcp-2.19.0.dist-info}/licenses/LICENSE +0 -0
  192. {adcp-2.17.0.dist-info → adcp-2.19.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: protocols/adcp_extension.json
3
- # timestamp: 2025-11-29T12:00:45+00:00
3
+ # timestamp: 2026-01-14T17:08:13+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -8,30 +8,46 @@ from enum import Enum
8
8
  from typing import Annotated
9
9
 
10
10
  from adcp.types.base import AdCPBaseModel
11
- from pydantic import ConfigDict, Field
11
+ from pydantic import ConfigDict, Field, RootModel
12
+
13
+
14
+ class ExtensionsSupportedItem(RootModel[str]):
15
+ root: Annotated[
16
+ str,
17
+ Field(
18
+ description="Extension namespace (e.g., 'sustainability'). Must be lowercase alphanumeric with underscores.",
19
+ pattern="^[a-z][a-z0-9_]*$",
20
+ ),
21
+ ]
12
22
 
13
23
 
14
24
  class ProtocolsSupportedEnum(Enum):
15
- media_buy = 'media_buy'
16
- creative = 'creative'
17
- signals = 'signals'
25
+ media_buy = "media_buy"
26
+ creative = "creative"
27
+ signals = "signals"
18
28
 
19
29
 
20
- class AdcpAgentCardExtension(AdCPBaseModel):
30
+ class AdcpAgentCardExtensionParams(AdCPBaseModel):
21
31
  model_config = ConfigDict(
22
- extra='forbid',
32
+ extra="allow",
23
33
  )
24
34
  adcp_version: Annotated[
25
35
  str,
26
36
  Field(
27
- description="Semantic version of the AdCP specification this agent implements (e.g., '2.4.0')",
28
- pattern='^\\d+\\.\\d+\\.\\d+$',
37
+ description="Semantic version of the AdCP specification this agent implements (e.g., '2.5.0'). Extension schemas are versioned along with the AdCP spec.",
38
+ pattern="^\\d+\\.\\d+\\.\\d+$",
29
39
  ),
30
40
  ]
41
+ extensions_supported: Annotated[
42
+ list[ExtensionsSupportedItem] | None,
43
+ Field(
44
+ description="Typed extensions this agent supports. Each extension has a formal schema in /schemas/extensions/. Extension version is determined by adcp_version."
45
+ ),
46
+ ] = None
31
47
  protocols_supported: Annotated[
32
48
  list[ProtocolsSupportedEnum],
33
49
  Field(
34
- description='AdCP protocol domains supported by this agent. At least one must be specified.',
50
+ description="AdCP protocol domains supported by this agent. At least one must be specified.",
35
51
  min_length=1,
36
52
  ),
37
53
  ]
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: signals/activate_signal_request.json
3
- # timestamp: 2025-12-11T15:09:37+00:00
3
+ # timestamp: 2026-01-08T19:25:24+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -16,17 +16,17 @@ from ..core import ext as ext_1
16
16
 
17
17
  class ActivateSignalRequest(AdCPBaseModel):
18
18
  model_config = ConfigDict(
19
- extra='forbid',
19
+ extra="allow",
20
20
  )
21
21
  context: context_1.ContextObject | None = None
22
22
  deployments: Annotated[
23
23
  list[destination.Destination],
24
24
  Field(
25
- description='Target deployment(s) for activation. If the authenticated caller matches one of these deployment targets, activation keys will be included in the response.',
25
+ description="Target deployment(s) for activation. If the authenticated caller matches one of these deployment targets, activation keys will be included in the response.",
26
26
  min_length=1,
27
27
  ),
28
28
  ]
29
29
  ext: ext_1.ExtensionObject | None = None
30
30
  signal_agent_segment_id: Annotated[
31
- str, Field(description='The universal identifier for the signal to activate')
31
+ str, Field(description="The universal identifier for the signal to activate")
32
32
  ]
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: signals/activate_signal_response.json
3
- # timestamp: 2025-12-11T15:09:37+00:00
3
+ # timestamp: 2026-01-08T19:25:24+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -16,25 +16,25 @@ from ..core import ext as ext_1
16
16
 
17
17
  class ActivateSignalResponse1(AdCPBaseModel):
18
18
  model_config = ConfigDict(
19
- extra='forbid',
19
+ extra="allow",
20
20
  )
21
21
  context: context_1.ContextObject | None = None
22
22
  deployments: Annotated[
23
23
  list[deployment.Deployment],
24
- Field(description='Array of deployment results for each deployment target'),
24
+ Field(description="Array of deployment results for each deployment target"),
25
25
  ]
26
26
  ext: ext_1.ExtensionObject | None = None
27
27
 
28
28
 
29
29
  class ActivateSignalResponse2(AdCPBaseModel):
30
30
  model_config = ConfigDict(
31
- extra='forbid',
31
+ extra="allow",
32
32
  )
33
33
  context: context_1.ContextObject | None = None
34
34
  errors: Annotated[
35
35
  list[error.Error],
36
36
  Field(
37
- description='Array of errors explaining why activation failed (e.g., platform connectivity issues, signal definition problems, authentication failures)',
37
+ description="Array of errors explaining why activation failed (e.g., platform connectivity issues, signal definition problems, authentication failures)",
38
38
  min_length=1,
39
39
  ),
40
40
  ]
@@ -45,7 +45,7 @@ class ActivateSignalResponse(RootModel[ActivateSignalResponse1 | ActivateSignalR
45
45
  root: Annotated[
46
46
  ActivateSignalResponse1 | ActivateSignalResponse2,
47
47
  Field(
48
- description='Response payload for activate_signal task. Returns either complete success data OR error information, never both. This enforces atomic operation semantics - the signal is either fully activated or not activated at all.',
49
- title='Activate Signal Response',
48
+ description="Response payload for activate_signal task. Returns either complete success data OR error information, never both. This enforces atomic operation semantics - the signal is either fully activated or not activated at all.",
49
+ title="Activate Signal Response",
50
50
  ),
51
51
  ]
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: signals/get_signals_request.json
3
- # timestamp: 2025-12-11T15:09:37+00:00
3
+ # timestamp: 2026-01-08T19:25:24+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -16,20 +16,20 @@ from ..core import signal_filters
16
16
 
17
17
 
18
18
  class Country(RootModel[str]):
19
- root: Annotated[str, Field(pattern='^[A-Z]{2}$')]
19
+ root: Annotated[str, Field(pattern="^[A-Z]{2}$")]
20
20
 
21
21
 
22
22
  class DeliverTo(AdCPBaseModel):
23
23
  model_config = ConfigDict(
24
- extra='forbid',
24
+ extra="allow",
25
25
  )
26
26
  countries: Annotated[
27
- list[Country], Field(description='Countries where signals will be used (ISO codes)')
27
+ list[Country], Field(description="Countries where signals will be used (ISO codes)")
28
28
  ]
29
29
  deployments: Annotated[
30
30
  list[destination.Destination],
31
31
  Field(
32
- description='List of deployment targets (DSPs, sales agents, etc.). If the authenticated caller matches one of these deployment targets, activation keys will be included in the response.',
32
+ description="List of deployment targets (DSPs, sales agents, etc.). If the authenticated caller matches one of these deployment targets, activation keys will be included in the response.",
33
33
  min_length=1,
34
34
  ),
35
35
  ]
@@ -37,17 +37,17 @@ class DeliverTo(AdCPBaseModel):
37
37
 
38
38
  class GetSignalsRequest(AdCPBaseModel):
39
39
  model_config = ConfigDict(
40
- extra='forbid',
40
+ extra="allow",
41
41
  )
42
42
  context: context_1.ContextObject | None = None
43
43
  deliver_to: Annotated[
44
- DeliverTo, Field(description='Deployment targets where signals need to be activated')
44
+ DeliverTo, Field(description="Deployment targets where signals need to be activated")
45
45
  ]
46
46
  ext: ext_1.ExtensionObject | None = None
47
47
  filters: signal_filters.SignalFilters | None = None
48
48
  max_results: Annotated[
49
- int | None, Field(description='Maximum number of results to return', ge=1)
49
+ int | None, Field(description="Maximum number of results to return", ge=1)
50
50
  ] = None
51
51
  signal_spec: Annotated[
52
- str, Field(description='Natural language description of the desired signals')
52
+ str, Field(description="Natural language description of the desired signals")
53
53
  ]
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: signals/get_signals_response.json
3
- # timestamp: 2025-12-11T15:09:37+00:00
3
+ # timestamp: 2026-01-08T19:25:24+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -17,42 +17,42 @@ from ..enums import signal_catalog_type
17
17
 
18
18
  class Pricing(AdCPBaseModel):
19
19
  model_config = ConfigDict(
20
- extra='forbid',
20
+ extra="allow",
21
21
  )
22
- cpm: Annotated[float, Field(description='Cost per thousand impressions', ge=0.0)]
23
- currency: Annotated[str, Field(description='Currency code', pattern='^[A-Z]{3}$')]
22
+ cpm: Annotated[float, Field(description="Cost per thousand impressions", ge=0.0)]
23
+ currency: Annotated[str, Field(description="Currency code", pattern="^[A-Z]{3}$")]
24
24
 
25
25
 
26
26
  class Signal(AdCPBaseModel):
27
27
  model_config = ConfigDict(
28
- extra='forbid',
28
+ extra="allow",
29
29
  )
30
30
  coverage_percentage: Annotated[
31
- float, Field(description='Percentage of audience coverage', ge=0.0, le=100.0)
31
+ float, Field(description="Percentage of audience coverage", ge=0.0, le=100.0)
32
32
  ]
33
- data_provider: Annotated[str, Field(description='Name of the data provider')]
33
+ data_provider: Annotated[str, Field(description="Name of the data provider")]
34
34
  deployments: Annotated[
35
- list[deployment.Deployment], Field(description='Array of deployment targets')
35
+ list[deployment.Deployment], Field(description="Array of deployment targets")
36
36
  ]
37
- description: Annotated[str, Field(description='Detailed signal description')]
38
- name: Annotated[str, Field(description='Human-readable signal name')]
39
- pricing: Annotated[Pricing, Field(description='Pricing information')]
40
- signal_agent_segment_id: Annotated[str, Field(description='Unique identifier for the signal')]
37
+ description: Annotated[str, Field(description="Detailed signal description")]
38
+ name: Annotated[str, Field(description="Human-readable signal name")]
39
+ pricing: Annotated[Pricing, Field(description="Pricing information")]
40
+ signal_agent_segment_id: Annotated[str, Field(description="Unique identifier for the signal")]
41
41
  signal_type: Annotated[
42
- signal_catalog_type.SignalCatalogType, Field(description='Type of signal')
42
+ signal_catalog_type.SignalCatalogType, Field(description="Type of signal")
43
43
  ]
44
44
 
45
45
 
46
46
  class GetSignalsResponse(AdCPBaseModel):
47
47
  model_config = ConfigDict(
48
- extra='forbid',
48
+ extra="allow",
49
49
  )
50
50
  context: context_1.ContextObject | None = None
51
51
  errors: Annotated[
52
52
  list[error.Error] | None,
53
53
  Field(
54
- description='Task-specific errors and warnings (e.g., signal discovery or pricing issues)'
54
+ description="Task-specific errors and warnings (e.g., signal discovery or pricing issues)"
55
55
  ),
56
56
  ] = None
57
57
  ext: ext_1.ExtensionObject | None = None
58
- signals: Annotated[list[Signal], Field(description='Array of matching signals')]
58
+ signals: Annotated[list[Signal], Field(description="Array of matching signals")]
adcp/utils/__init__.py CHANGED
@@ -2,6 +2,29 @@ from __future__ import annotations
2
2
 
3
3
  """Utility functions."""
4
4
 
5
+ from adcp.utils.format_assets import (
6
+ get_asset_count,
7
+ get_format_assets,
8
+ get_individual_assets,
9
+ get_optional_assets,
10
+ get_repeatable_groups,
11
+ get_required_assets,
12
+ has_assets,
13
+ normalize_assets_required,
14
+ uses_deprecated_assets_field,
15
+ )
5
16
  from adcp.utils.operation_id import create_operation_id
6
17
 
7
- __all__ = ["create_operation_id"]
18
+ __all__ = [
19
+ "create_operation_id",
20
+ # Format asset utilities
21
+ "get_format_assets",
22
+ "normalize_assets_required",
23
+ "get_required_assets",
24
+ "get_optional_assets",
25
+ "get_individual_assets",
26
+ "get_repeatable_groups",
27
+ "uses_deprecated_assets_field",
28
+ "get_asset_count",
29
+ "has_assets",
30
+ ]
@@ -0,0 +1,224 @@
1
+ """Format Asset Utilities.
2
+
3
+ Provides backward-compatible access to format assets.
4
+ The v2.6 `assets` field replaces the deprecated `assets_required` field.
5
+
6
+ These utilities help users work with format assets regardless of which field
7
+ the agent uses, providing a smooth migration path.
8
+
9
+ Example:
10
+ ```python
11
+ from adcp import Format
12
+ from adcp.utils.format_assets import get_format_assets, get_required_assets
13
+
14
+ # Get all assets from a format (handles both new and deprecated fields)
15
+ all_assets = get_format_assets(format)
16
+
17
+ # Get only required assets
18
+ required = get_required_assets(format)
19
+
20
+ # Check if using deprecated field
21
+ if uses_deprecated_assets_field(format):
22
+ print("Agent should migrate to 'assets' field")
23
+ ```
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from typing import TYPE_CHECKING, Any, Union
29
+
30
+ from adcp.types.generated_poc.core.format import Assets as AssetsModel
31
+ from adcp.types.generated_poc.core.format import Assets1 as Assets1Model
32
+
33
+ if TYPE_CHECKING:
34
+ from adcp.types.generated_poc.core.format import Assets, Assets1, Format
35
+
36
+ # Type alias for any format asset (individual or repeatable group)
37
+ FormatAsset = Union["Assets", "Assets1"]
38
+
39
+
40
+ def get_format_assets(format: Format) -> list[FormatAsset]:
41
+ """Get assets from a Format, preferring new `assets` field, falling back to `assets_required`.
42
+
43
+ This provides backward compatibility during the migration from `assets_required` to `assets`.
44
+ - If `assets` exists and has items, returns it directly
45
+ - If only `assets_required` exists, normalizes it to the new format (sets required=True)
46
+ - Returns empty list if neither field exists (flexible format with no assets)
47
+
48
+ Args:
49
+ format: The Format object from list_creative_formats response
50
+
51
+ Returns:
52
+ List of assets in the new format structure
53
+
54
+ Example:
55
+ ```python
56
+ formats = await agent.simple.list_creative_formats()
57
+ for format in formats.formats:
58
+ assets = get_format_assets(format)
59
+ print(f"{format.name} has {len(assets)} assets")
60
+ ```
61
+ """
62
+ # Prefer new `assets` field (v2.6+)
63
+ if format.assets and len(format.assets) > 0:
64
+ return list(format.assets)
65
+
66
+ # Fall back to deprecated `assets_required` and normalize
67
+ if format.assets_required and len(format.assets_required) > 0:
68
+ return normalize_assets_required(format.assets_required)
69
+
70
+ return []
71
+
72
+
73
+ def normalize_assets_required(assets_required: list[Any]) -> list[FormatAsset]:
74
+ """Convert deprecated assets_required to new assets format.
75
+
76
+ All assets in assets_required are required by definition (that's why they were in
77
+ that array). The new `assets` field has an explicit `required: boolean` to allow
78
+ both required AND optional assets.
79
+
80
+ Args:
81
+ assets_required: The deprecated assets_required array
82
+
83
+ Returns:
84
+ Normalized assets as Pydantic models with explicit required=True
85
+ """
86
+ normalized: list[FormatAsset] = []
87
+ for asset in assets_required:
88
+ # Get asset data as dict
89
+ if isinstance(asset, dict):
90
+ asset_dict = asset
91
+ else:
92
+ asset_dict = asset.model_dump() if hasattr(asset, "model_dump") else dict(asset)
93
+
94
+ # Check if it's a repeatable group (has asset_group_id) or individual asset
95
+ if "asset_group_id" in asset_dict:
96
+ # Repeatable group - use Assets1Model
97
+ normalized.append(Assets1Model(**{**asset_dict, "required": True}))
98
+ else:
99
+ # Individual asset - use AssetsModel
100
+ normalized.append(AssetsModel(**{**asset_dict, "required": True}))
101
+
102
+ return normalized
103
+
104
+
105
+ def get_required_assets(format: Format) -> list[FormatAsset]:
106
+ """Get only required assets from a Format.
107
+
108
+ Args:
109
+ format: The Format object
110
+
111
+ Returns:
112
+ List of required assets only
113
+
114
+ Example:
115
+ ```python
116
+ required_assets = get_required_assets(format)
117
+ print(f"Must provide {len(required_assets)} assets")
118
+ ```
119
+ """
120
+ return [asset for asset in get_format_assets(format) if _is_required(asset)]
121
+
122
+
123
+ def get_optional_assets(format: Format) -> list[FormatAsset]:
124
+ """Get only optional assets from a Format.
125
+
126
+ Note: When using deprecated `assets_required`, this will always return empty
127
+ since assets_required only contained required assets.
128
+
129
+ Args:
130
+ format: The Format object
131
+
132
+ Returns:
133
+ List of optional assets only
134
+
135
+ Example:
136
+ ```python
137
+ optional_assets = get_optional_assets(format)
138
+ print(f"Can optionally provide {len(optional_assets)} additional assets")
139
+ ```
140
+ """
141
+ return [asset for asset in get_format_assets(format) if not _is_required(asset)]
142
+
143
+
144
+ def get_individual_assets(format: Format) -> list[FormatAsset]:
145
+ """Get individual assets (not repeatable groups) from a Format.
146
+
147
+ Args:
148
+ format: The Format object
149
+
150
+ Returns:
151
+ List of individual assets (item_type='individual')
152
+ """
153
+ return [asset for asset in get_format_assets(format) if _get_item_type(asset) == "individual"]
154
+
155
+
156
+ def get_repeatable_groups(format: Format) -> list[FormatAsset]:
157
+ """Get repeatable asset groups from a Format.
158
+
159
+ Args:
160
+ format: The Format object
161
+
162
+ Returns:
163
+ List of repeatable asset groups (item_type='repeatable_group')
164
+ """
165
+ return [
166
+ asset for asset in get_format_assets(format) if _get_item_type(asset) == "repeatable_group"
167
+ ]
168
+
169
+
170
+ def uses_deprecated_assets_field(format: Format) -> bool:
171
+ """Check if format uses deprecated assets_required field (for migration warnings).
172
+
173
+ Args:
174
+ format: The Format object
175
+
176
+ Returns:
177
+ True if using deprecated field, False if using new field or neither
178
+
179
+ Example:
180
+ ```python
181
+ if uses_deprecated_assets_field(format):
182
+ print(f"Format {format.name} uses deprecated assets_required field")
183
+ ```
184
+ """
185
+ has_assets = format.assets is not None and len(format.assets) > 0
186
+ has_assets_required = format.assets_required is not None and len(format.assets_required) > 0
187
+ return not has_assets and has_assets_required
188
+
189
+
190
+ def get_asset_count(format: Format) -> int:
191
+ """Get the count of assets in a format (for display purposes).
192
+
193
+ Args:
194
+ format: The Format object
195
+
196
+ Returns:
197
+ Number of assets, or 0 if none defined
198
+ """
199
+ return len(get_format_assets(format))
200
+
201
+
202
+ def has_assets(format: Format) -> bool:
203
+ """Check if a format has any assets defined.
204
+
205
+ Args:
206
+ format: The Format object
207
+
208
+ Returns:
209
+ True if format has assets, False otherwise
210
+ """
211
+ return get_asset_count(format) > 0
212
+
213
+
214
+ # Internal helpers
215
+
216
+
217
+ def _is_required(asset: FormatAsset) -> bool:
218
+ """Check if an asset is required."""
219
+ return getattr(asset, "required", False) is True
220
+
221
+
222
+ def _get_item_type(asset: FormatAsset) -> str:
223
+ """Get the item_type of an asset."""
224
+ return getattr(asset, "item_type", "individual")
@@ -399,25 +399,47 @@ def _create_sample_manifest_for_format(fmt: Format) -> CreativeManifest | None:
399
399
  Sample CreativeManifest, or None if unable to create one
400
400
  """
401
401
  from adcp.types import CreativeManifest
402
+ from adcp.utils.format_assets import get_required_assets
402
403
 
403
- if not fmt.assets_required:
404
+ required_assets = get_required_assets(fmt)
405
+ if not required_assets:
404
406
  return None
405
407
 
406
408
  assets: dict[str, Any] = {}
407
409
 
408
- for asset in fmt.assets_required:
410
+ for asset in required_assets:
409
411
  if isinstance(asset, dict):
412
+ # Handle dict input
410
413
  asset_id = asset.get("asset_id")
411
414
  asset_type = asset.get("asset_type")
412
415
 
413
416
  if asset_id:
414
417
  assets[asset_id] = _create_sample_asset(asset_type)
415
418
  else:
416
- # Handle Pydantic model
417
- asset_id = asset.asset_id
418
- has_value = hasattr(asset.asset_type, "value")
419
- asset_type = asset.asset_type.value if has_value else str(asset.asset_type)
420
- assets[asset_id] = _create_sample_asset(asset_type)
419
+ # Handle Pydantic model - check for individual vs repeatable_group
420
+ item_type = getattr(asset, "item_type", "individual")
421
+
422
+ if item_type == "individual":
423
+ asset_id = asset.asset_id
424
+ has_value = hasattr(asset.asset_type, "value")
425
+ asset_type = asset.asset_type.value if has_value else str(asset.asset_type)
426
+ assets[asset_id] = _create_sample_asset(asset_type)
427
+ elif item_type == "repeatable_group":
428
+ # For repeatable groups, create sample assets for each asset in the group
429
+ group_assets = getattr(asset, "assets", [])
430
+ for group_asset in group_assets:
431
+ if isinstance(group_asset, dict):
432
+ asset_id = group_asset.get("asset_id")
433
+ asset_type = group_asset.get("asset_type")
434
+ else:
435
+ asset_id = group_asset.asset_id
436
+ if hasattr(group_asset.asset_type, "value"):
437
+ asset_type = group_asset.asset_type.value
438
+ else:
439
+ asset_type = str(group_asset.asset_type)
440
+
441
+ if asset_id:
442
+ assets[asset_id] = _create_sample_asset(asset_type)
421
443
 
422
444
  if not assets:
423
445
  return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adcp
3
- Version: 2.17.0
3
+ Version: 2.19.0
4
4
  Summary: Official Python client for the Ad Context Protocol (AdCP)
5
5
  Author-email: AdCP Community <maintainers@adcontextprotocol.org>
6
6
  License: Apache-2.0