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.
- adcp/ADCP_VERSION +1 -1
- adcp/__init__.py +22 -1
- adcp/__main__.py +72 -0
- adcp/protocols/mcp.py +3 -1
- adcp/types/_ergonomic.py +0 -4
- adcp/types/_generated.py +91 -4
- adcp/types/generated_poc/adagents.py +239 -149
- adcp/types/generated_poc/core/activation_key.py +9 -9
- adcp/types/generated_poc/core/assets/audio_asset.py +6 -6
- adcp/types/generated_poc/core/assets/css_asset.py +3 -3
- adcp/types/generated_poc/core/assets/daast_asset.py +19 -19
- adcp/types/generated_poc/core/assets/html_asset.py +3 -3
- adcp/types/generated_poc/core/assets/image_asset.py +7 -7
- adcp/types/generated_poc/core/assets/javascript_asset.py +4 -4
- adcp/types/generated_poc/core/assets/text_asset.py +3 -3
- adcp/types/generated_poc/core/assets/url_asset.py +4 -4
- adcp/types/generated_poc/core/assets/vast_asset.py +19 -19
- adcp/types/generated_poc/core/assets/video_asset.py +8 -8
- adcp/types/generated_poc/core/assets/webhook_asset.py +10 -10
- adcp/types/generated_poc/core/async_response_data.py +2 -2
- adcp/types/generated_poc/core/brand_manifest.py +56 -56
- adcp/types/generated_poc/core/brand_manifest_ref.py +9 -9
- adcp/types/generated_poc/core/context.py +2 -3
- adcp/types/generated_poc/core/creative_asset.py +14 -14
- adcp/types/generated_poc/core/creative_assignment.py +4 -4
- adcp/types/generated_poc/core/creative_filters.py +20 -20
- adcp/types/generated_poc/core/creative_manifest.py +3 -3
- adcp/types/generated_poc/core/creative_policy.py +5 -5
- adcp/types/generated_poc/core/delivery_metrics.py +33 -33
- adcp/types/generated_poc/core/deployment.py +21 -21
- adcp/types/generated_poc/core/destination.py +12 -12
- adcp/types/generated_poc/core/error.py +9 -9
- adcp/types/generated_poc/core/ext.py +2 -3
- adcp/types/generated_poc/core/format.py +139 -51
- adcp/types/generated_poc/core/format_id.py +6 -6
- adcp/types/generated_poc/core/frequency_cap.py +3 -3
- adcp/types/generated_poc/core/identifier.py +27 -0
- adcp/types/generated_poc/core/mcp_webhook_payload.py +10 -10
- adcp/types/generated_poc/core/measurement.py +9 -9
- adcp/types/generated_poc/core/media_buy.py +8 -8
- adcp/types/generated_poc/core/package.py +9 -9
- adcp/types/generated_poc/core/performance_feedback.py +19 -19
- adcp/types/generated_poc/core/placement.py +5 -5
- adcp/types/generated_poc/core/pricing_option.py +2 -2
- adcp/types/generated_poc/core/product.py +21 -21
- adcp/types/generated_poc/core/product_filters.py +19 -19
- adcp/types/generated_poc/core/promoted_offerings.py +21 -21
- adcp/types/generated_poc/core/promoted_products.py +3 -3
- adcp/types/generated_poc/core/property.py +10 -10
- adcp/types/generated_poc/core/property_id.py +4 -4
- adcp/types/generated_poc/core/property_list_ref.py +26 -0
- adcp/types/generated_poc/core/property_tag.py +4 -4
- adcp/types/generated_poc/core/protocol_envelope.py +9 -9
- adcp/types/generated_poc/core/publisher_property_selector.py +14 -14
- adcp/types/generated_poc/core/push_notification_config.py +5 -5
- adcp/types/generated_poc/core/reporting_capabilities.py +9 -9
- adcp/types/generated_poc/core/response.py +5 -5
- adcp/types/generated_poc/core/signal_filters.py +6 -6
- adcp/types/generated_poc/core/start_timing.py +5 -5
- adcp/types/generated_poc/core/sub_asset.py +15 -15
- adcp/types/generated_poc/core/targeting.py +9 -9
- adcp/types/generated_poc/creative/list_creative_formats_request.py +21 -21
- adcp/types/generated_poc/creative/list_creative_formats_response.py +6 -6
- adcp/types/generated_poc/creative/preview_creative_request.py +26 -26
- adcp/types/generated_poc/creative/preview_creative_response.py +31 -30
- adcp/types/generated_poc/creative/preview_render.py +26 -26
- adcp/types/generated_poc/enums/adcp_domain.py +5 -3
- adcp/types/generated_poc/enums/asset_content_type.py +13 -13
- adcp/types/generated_poc/enums/auth_scheme.py +2 -2
- adcp/types/generated_poc/enums/available_metric.py +9 -9
- adcp/types/generated_poc/enums/channels.py +9 -9
- adcp/types/generated_poc/enums/co_branding_requirement.py +3 -3
- adcp/types/generated_poc/enums/creative_action.py +5 -5
- adcp/types/generated_poc/enums/creative_agent_capability.py +4 -4
- adcp/types/generated_poc/enums/creative_sort_field.py +6 -6
- adcp/types/generated_poc/enums/creative_status.py +4 -4
- adcp/types/generated_poc/enums/daast_tracking_event.py +11 -11
- adcp/types/generated_poc/enums/daast_version.py +2 -2
- adcp/types/generated_poc/enums/delivery_type.py +2 -2
- adcp/types/generated_poc/enums/dimension_unit.py +4 -4
- adcp/types/generated_poc/enums/feed_format.py +3 -3
- adcp/types/generated_poc/enums/feedback_source.py +4 -4
- adcp/types/generated_poc/enums/format_category.py +7 -7
- adcp/types/generated_poc/enums/format_id_parameter.py +2 -2
- adcp/types/generated_poc/enums/frequency_cap_scope.py +3 -3
- adcp/types/generated_poc/enums/history_entry_type.py +2 -2
- adcp/types/generated_poc/enums/http_method.py +2 -2
- adcp/types/generated_poc/enums/identifier_types.py +19 -19
- adcp/types/generated_poc/enums/javascript_module_type.py +3 -3
- adcp/types/generated_poc/enums/landing_page_requirement.py +3 -3
- adcp/types/generated_poc/enums/markdown_flavor.py +2 -2
- adcp/types/generated_poc/enums/media_buy_status.py +4 -4
- adcp/types/generated_poc/enums/metric_type.py +8 -8
- adcp/types/generated_poc/enums/notification_type.py +4 -4
- adcp/types/generated_poc/enums/pacing.py +3 -3
- adcp/types/generated_poc/enums/preview_output_format.py +2 -2
- adcp/types/generated_poc/enums/pricing_model.py +7 -7
- adcp/types/generated_poc/enums/property_type.py +7 -7
- adcp/types/generated_poc/enums/publisher_identifier_types.py +5 -5
- adcp/types/generated_poc/enums/reporting_frequency.py +3 -3
- adcp/types/generated_poc/enums/signal_catalog_type.py +3 -3
- adcp/types/generated_poc/enums/sort_direction.py +2 -2
- adcp/types/generated_poc/enums/standard_format_ids.py +35 -35
- adcp/types/generated_poc/enums/task_status.py +9 -9
- adcp/types/generated_poc/enums/task_type.py +12 -6
- adcp/types/generated_poc/enums/update_frequency.py +4 -4
- adcp/types/generated_poc/enums/url_asset_type.py +3 -3
- adcp/types/generated_poc/enums/validation_mode.py +2 -2
- adcp/types/generated_poc/enums/vast_tracking_event.py +16 -16
- adcp/types/generated_poc/enums/vast_version.py +5 -5
- adcp/types/generated_poc/enums/webhook_response_type.py +4 -4
- adcp/types/generated_poc/enums/webhook_security_method.py +3 -3
- adcp/types/generated_poc/extensions/__init__.py +3 -0
- adcp/types/generated_poc/extensions/extension_meta.py +50 -0
- adcp/types/generated_poc/media_buy/build_creative_request.py +5 -5
- adcp/types/generated_poc/media_buy/build_creative_response.py +7 -7
- adcp/types/generated_poc/media_buy/create_media_buy_async_response_input_required.py +6 -6
- adcp/types/generated_poc/media_buy/create_media_buy_async_response_submitted.py +2 -2
- adcp/types/generated_poc/media_buy/create_media_buy_async_response_working.py +6 -6
- adcp/types/generated_poc/media_buy/create_media_buy_request.py +64 -23
- adcp/types/generated_poc/media_buy/create_media_buy_response.py +8 -8
- adcp/types/generated_poc/media_buy/get_media_buy_delivery_request.py +9 -9
- adcp/types/generated_poc/media_buy/get_media_buy_delivery_response.py +52 -52
- adcp/types/generated_poc/media_buy/get_products_async_response_input_required.py +7 -7
- adcp/types/generated_poc/media_buy/get_products_async_response_submitted.py +3 -3
- adcp/types/generated_poc/media_buy/get_products_async_response_working.py +5 -5
- adcp/types/generated_poc/media_buy/get_products_request.py +11 -5
- adcp/types/generated_poc/media_buy/get_products_response.py +10 -4
- adcp/types/generated_poc/media_buy/list_authorized_properties_request.py +4 -4
- adcp/types/generated_poc/media_buy/list_authorized_properties_response.py +8 -8
- adcp/types/generated_poc/media_buy/list_creative_formats_request.py +10 -10
- adcp/types/generated_poc/media_buy/list_creative_formats_response.py +6 -6
- adcp/types/generated_poc/media_buy/list_creatives_request.py +24 -24
- adcp/types/generated_poc/media_buy/list_creatives_response.py +53 -53
- adcp/types/generated_poc/media_buy/package_request.py +16 -7
- adcp/types/generated_poc/media_buy/provide_performance_feedback_request.py +20 -20
- adcp/types/generated_poc/media_buy/provide_performance_feedback_response.py +7 -7
- adcp/types/generated_poc/media_buy/sync_creatives_async_response_input_required.py +6 -6
- adcp/types/generated_poc/media_buy/sync_creatives_async_response_submitted.py +2 -2
- adcp/types/generated_poc/media_buy/sync_creatives_async_response_working.py +8 -8
- adcp/types/generated_poc/media_buy/sync_creatives_request.py +8 -8
- adcp/types/generated_poc/media_buy/sync_creatives_response.py +16 -16
- adcp/types/generated_poc/media_buy/update_media_buy_async_response_input_required.py +5 -5
- adcp/types/generated_poc/media_buy/update_media_buy_async_response_submitted.py +2 -2
- adcp/types/generated_poc/media_buy/update_media_buy_async_response_working.py +6 -6
- adcp/types/generated_poc/media_buy/update_media_buy_request.py +37 -29
- adcp/types/generated_poc/media_buy/update_media_buy_response.py +8 -8
- adcp/types/generated_poc/pricing_options/cpc_option.py +9 -9
- adcp/types/generated_poc/pricing_options/cpcv_option.py +9 -9
- adcp/types/generated_poc/pricing_options/cpm_auction_option.py +14 -14
- adcp/types/generated_poc/pricing_options/cpm_fixed_option.py +9 -9
- adcp/types/generated_poc/pricing_options/cpp_option.py +14 -14
- adcp/types/generated_poc/pricing_options/cpv_option.py +13 -13
- adcp/types/generated_poc/pricing_options/flat_rate_option.py +16 -16
- adcp/types/generated_poc/pricing_options/vcpm_auction_option.py +14 -14
- adcp/types/generated_poc/pricing_options/vcpm_fixed_option.py +9 -9
- adcp/types/generated_poc/property/__init__.py +3 -0
- adcp/types/generated_poc/property/base_property_source.py +86 -0
- adcp/types/generated_poc/property/create_property_list_request.py +43 -0
- adcp/types/generated_poc/property/create_property_list_response.py +27 -0
- adcp/types/generated_poc/property/delete_property_list_request.py +22 -0
- adcp/types/generated_poc/property/delete_property_list_response.py +21 -0
- adcp/types/generated_poc/property/feature_requirement.py +42 -0
- adcp/types/generated_poc/property/get_property_list_request.py +34 -0
- adcp/types/generated_poc/property/get_property_list_response.py +61 -0
- adcp/types/generated_poc/property/list_property_features_request.py +25 -0
- adcp/types/generated_poc/property/list_property_features_response.py +24 -0
- adcp/types/generated_poc/property/list_property_lists_request.py +29 -0
- adcp/types/generated_poc/property/list_property_lists_response.py +39 -0
- adcp/types/generated_poc/property/property_error.py +33 -0
- adcp/types/generated_poc/property/property_feature.py +22 -0
- adcp/types/generated_poc/property/property_feature_definition.py +80 -0
- adcp/types/generated_poc/property/property_list.py +62 -0
- adcp/types/generated_poc/property/property_list_changed_webhook.py +51 -0
- adcp/types/generated_poc/property/property_list_filters.py +47 -0
- adcp/types/generated_poc/property/update_property_list_request.py +46 -0
- adcp/types/generated_poc/property/update_property_list_response.py +21 -0
- adcp/types/generated_poc/protocols/adcp_extension.py +26 -10
- adcp/types/generated_poc/signals/activate_signal_request.py +4 -4
- adcp/types/generated_poc/signals/activate_signal_response.py +7 -7
- adcp/types/generated_poc/signals/get_signals_request.py +9 -9
- adcp/types/generated_poc/signals/get_signals_response.py +16 -16
- adcp/utils/__init__.py +24 -1
- adcp/utils/format_assets.py +224 -0
- adcp/utils/preview_cache.py +29 -7
- {adcp-2.17.0.dist-info → adcp-2.19.0.dist-info}/METADATA +1 -1
- adcp-2.19.0.dist-info/RECORD +220 -0
- adcp-2.17.0.dist-info/RECORD +0 -194
- {adcp-2.17.0.dist-info → adcp-2.19.0.dist-info}/WHEEL +0 -0
- {adcp-2.17.0.dist-info → adcp-2.19.0.dist-info}/entry_points.txt +0 -0
- {adcp-2.17.0.dist-info → adcp-2.19.0.dist-info}/licenses/LICENSE +0 -0
- {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:
|
|
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 =
|
|
16
|
-
creative =
|
|
17
|
-
signals =
|
|
25
|
+
media_buy = "media_buy"
|
|
26
|
+
creative = "creative"
|
|
27
|
+
signals = "signals"
|
|
18
28
|
|
|
19
29
|
|
|
20
|
-
class
|
|
30
|
+
class AdcpAgentCardExtensionParams(AdCPBaseModel):
|
|
21
31
|
model_config = ConfigDict(
|
|
22
|
-
extra=
|
|
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.
|
|
28
|
-
pattern=
|
|
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=
|
|
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:
|
|
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=
|
|
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=
|
|
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=
|
|
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:
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
49
|
-
title=
|
|
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:
|
|
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=
|
|
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=
|
|
24
|
+
extra="allow",
|
|
25
25
|
)
|
|
26
26
|
countries: Annotated[
|
|
27
|
-
list[Country], Field(description=
|
|
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=
|
|
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=
|
|
40
|
+
extra="allow",
|
|
41
41
|
)
|
|
42
42
|
context: context_1.ContextObject | None = None
|
|
43
43
|
deliver_to: Annotated[
|
|
44
|
-
DeliverTo, Field(description=
|
|
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=
|
|
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=
|
|
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:
|
|
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=
|
|
20
|
+
extra="allow",
|
|
21
21
|
)
|
|
22
|
-
cpm: Annotated[float, Field(description=
|
|
23
|
-
currency: Annotated[str, Field(description=
|
|
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=
|
|
28
|
+
extra="allow",
|
|
29
29
|
)
|
|
30
30
|
coverage_percentage: Annotated[
|
|
31
|
-
float, Field(description=
|
|
31
|
+
float, Field(description="Percentage of audience coverage", ge=0.0, le=100.0)
|
|
32
32
|
]
|
|
33
|
-
data_provider: Annotated[str, Field(description=
|
|
33
|
+
data_provider: Annotated[str, Field(description="Name of the data provider")]
|
|
34
34
|
deployments: Annotated[
|
|
35
|
-
list[deployment.Deployment], Field(description=
|
|
35
|
+
list[deployment.Deployment], Field(description="Array of deployment targets")
|
|
36
36
|
]
|
|
37
|
-
description: Annotated[str, Field(description=
|
|
38
|
-
name: Annotated[str, Field(description=
|
|
39
|
-
pricing: Annotated[Pricing, Field(description=
|
|
40
|
-
signal_agent_segment_id: Annotated[str, Field(description=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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__ = [
|
|
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")
|
adcp/utils/preview_cache.py
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|