Skip to content
This repository was archived by the owner on Mar 15, 2026. It is now read-only.

Commit f4c8635

Browse files
bokelleyclaude
andauthored
feat: upgrade adcp library to v3.1.0 (#45)
Migrate from direct `.assets_required` attribute access to using the new `get_required_assets()` helper function from the adcp library. Changes: - Update adcp dependency from >=2.18.0 to >=3.1.0 - Use get_required_assets() for filtering and iterating required assets - Update base.py to use .assets for building complete asset type maps - Simplify server.py by removing unused asset_group_id handling - Update all tests to use the new API Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent a3c394a commit f4c8635

9 files changed

Lines changed: 67 additions & 62 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ dependencies = [
1616
"boto3>=1.35.0",
1717
"markdown>=3.6",
1818
"bleach>=6.3.0",
19-
"adcp>=2.18.0", # Official ADCP Python client with template format support
19+
"adcp>=3.1.0", # Official ADCP Python client with template format support
2020
]
2121

2222
[project.scripts]

src/creative_agent/data/standard_formats.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from typing import Any
77

8-
from adcp import FormatCategory, FormatId
8+
from adcp import FormatCategory, FormatId, get_required_assets
99
from adcp.types.generated_poc.core.format import Assets as LibAssets
1010
from adcp.types.generated_poc.core.format import AssetsRequired as LibAssetsRequired
1111
from adcp.types.generated_poc.core.format import Renders as LibRender
@@ -1716,8 +1716,10 @@ def has_asset_type(req: Any, target_type: AssetType | str) -> bool:
17161716
results = [
17171717
fmt
17181718
for fmt in results
1719-
if fmt.assets_required
1720-
and all(any(has_asset_type(req, asset_type) for req in fmt.assets_required) for asset_type in asset_types)
1719+
if get_required_assets(fmt)
1720+
and all(
1721+
any(has_asset_type(req, asset_type) for req in get_required_assets(fmt)) for asset_type in asset_types
1722+
)
17211723
]
17221724

17231725
return results

src/creative_agent/renderers/base.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,15 @@ def build_asset_type_map(self, format_obj: Any) -> dict[str, str]:
8585
Dictionary mapping asset_id to asset_type string
8686
"""
8787
asset_type_map = {}
88-
if hasattr(format_obj, "assets_required") and format_obj.assets_required:
89-
for required_asset in format_obj.assets_required:
88+
if hasattr(format_obj, "assets") and format_obj.assets:
89+
for asset in format_obj.assets:
9090
# Handle both dict and object access
91-
if isinstance(required_asset, dict):
92-
asset_id = required_asset.get("asset_id")
93-
asset_type = required_asset.get("asset_type")
91+
if isinstance(asset, dict):
92+
asset_id = asset.get("asset_id")
93+
asset_type = asset.get("asset_type")
9494
else:
95-
asset_id = getattr(required_asset, "asset_id", None)
96-
asset_type = getattr(required_asset, "asset_type", None)
95+
asset_id = getattr(asset, "asset_id", None)
96+
asset_type = getattr(asset, "asset_type", None)
9797

9898
if asset_id and asset_type:
9999
# Handle enum or string asset_type

src/creative_agent/server.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -714,16 +714,12 @@ def build_creative(
714714
format_spec += f"Dimensions: {int(render.dimensions.width)}x{int(render.dimensions.height)}\n"
715715

716716
format_spec += "\nRequired Assets:\n"
717-
if output_fmt.assets_required:
718-
for asset_req in output_fmt.assets_required:
719-
# assets_required are always Pydantic models (adcp 2.2.0+)
720-
if hasattr(asset_req, "asset_group_id"):
721-
# Repeatable group (AssetsRequired1)
722-
format_spec += f"- {asset_req.asset_group_id} (repeatable group)\n"
723-
elif hasattr(asset_req, "asset_id"):
724-
# Individual asset (AssetsRequired)
725-
asset_type = getattr(asset_req, "asset_type", "unknown")
726-
format_spec += f"- {asset_req.asset_id} ({asset_type})\n"
717+
required_assets = get_required_assets(output_fmt)
718+
for asset_req in required_assets:
719+
asset_id = getattr(asset_req, "asset_id", None)
720+
asset_type = getattr(asset_req, "asset_type", "unknown")
721+
if asset_id:
722+
format_spec += f"- {asset_id} ({asset_type})\n"
727723

728724
# Add brand context if provided
729725
brand_context = ""

tests/integration/test_template_formats.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import json
88

9-
from adcp import FormatId, ListCreativeFormatsResponse
9+
from adcp import FormatId, ListCreativeFormatsResponse, get_required_assets
1010
from adcp.types.generated_poc.enums.format_id_parameter import FormatIdParameter
1111
from pydantic import AnyUrl
1212

@@ -215,19 +215,20 @@ def test_get_video_template_by_base_id(self):
215215
class TestTemplateAssetRequirements:
216216
"""Test asset requirements for template vs concrete formats."""
217217

218-
def test_template_formats_have_assets_required(self):
219-
"""Template formats should have assets_required defined."""
218+
def test_template_formats_have_required_assets(self):
219+
"""Template formats should have required assets defined."""
220220
# Get display_image template
221221
format_id = FormatId(agent_url=AnyUrl(str(AGENT_URL)), id="display_image")
222222
fmt = get_format_by_id(format_id)
223223

224224
assert fmt is not None
225-
assert fmt.assets_required is not None
226-
assert len(fmt.assets_required) > 0
225+
required_assets = get_required_assets(fmt)
226+
assert required_assets is not None
227+
assert len(required_assets) > 0
227228

228229
# Find the image asset
229230
image_asset = None
230-
for asset in fmt.assets_required:
231+
for asset in required_assets:
231232
if asset.asset_id == "banner_image":
232233
image_asset = asset
233234
break
@@ -241,12 +242,13 @@ def test_concrete_formats_have_explicit_requirements(self):
241242
fmt = get_format_by_id(format_id)
242243

243244
assert fmt is not None
244-
assert fmt.assets_required is not None
245-
assert len(fmt.assets_required) > 0
245+
required_assets = get_required_assets(fmt)
246+
assert required_assets is not None
247+
assert len(required_assets) > 0
246248

247249
# Find the image asset
248250
image_asset = None
249-
for asset in fmt.assets_required:
251+
for asset in required_assets:
250252
if asset.asset_id == "banner_image":
251253
image_asset = asset
252254
break

tests/integration/test_tool_response_formats.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
FormatId,
1616
ListCreativeFormatsResponse,
1717
PreviewCreativeResponse,
18+
get_required_assets,
1819
)
1920

2021
from creative_agent import server
@@ -118,16 +119,16 @@ def test_no_extra_wrapper_fields(self):
118119
f"Response must have required keys {expected_keys}, got {actual_keys}"
119120
)
120121

121-
def test_assets_required_have_asset_id(self):
122-
"""Per ADCP PR #135, all AssetsRequired must have asset_id field."""
122+
def test_assets_have_asset_id(self):
123+
"""Per ADCP PR #135, all assets must have asset_id field."""
123124
result = list_creative_formats()
124125
response = ListCreativeFormatsResponse.model_validate(result.structured_content)
125126

126-
formats_with_assets = [fmt for fmt in response.formats if fmt.assets_required]
127-
assert len(formats_with_assets) > 0, "Should have formats with assets_required"
127+
formats_with_assets = [fmt for fmt in response.formats if get_required_assets(fmt)]
128+
assert len(formats_with_assets) > 0, "Should have formats with required assets"
128129

129130
for fmt in formats_with_assets:
130-
for asset in fmt.assets_required:
131+
for asset in get_required_assets(fmt):
131132
# Access asset_id - will raise AttributeError if missing
132133
asset_dict = asset.model_dump() if hasattr(asset, "model_dump") else dict(asset)
133134
assert "asset_id" in asset_dict, f"Format {fmt.format_id.id} has asset without asset_id: {asset_dict}"

tests/unit/test_info_card_formats.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Tests for info card format definitions."""
22

3-
from adcp import FormatId
3+
from adcp import FormatId, get_required_assets
44

55
from creative_agent.data.format_types import AssetType, Type
66
from creative_agent.data.standard_formats import AGENT_URL, INFO_CARD_FORMATS, filter_formats
@@ -95,12 +95,13 @@ def test_requires_product_assets(self):
9595
assert fmt.assets
9696
assert len(fmt.assets) == 9 # 8 original + impression_tracker
9797

98-
# Check required assets are in assets_required (backward compatibility)
99-
assert fmt.assets_required
100-
assert len(fmt.assets_required) == 3 # Only required=True assets
98+
# Check required assets
99+
required_assets = get_required_assets(fmt)
100+
assert required_assets
101+
assert len(required_assets) == 3 # Only required=True assets
101102

102-
# Check required asset ids in assets_required
103-
required_asset_ids = {get_asset_attr(asset, "asset_id") for asset in fmt.assets_required}
103+
# Check required asset ids
104+
required_asset_ids = {get_asset_attr(asset, "asset_id") for asset in required_assets}
104105
assert "product_image" in required_asset_ids
105106
assert "product_name" in required_asset_ids
106107
assert "product_description" in required_asset_ids
@@ -114,7 +115,7 @@ def test_requires_product_assets(self):
114115
assert asset_type_map["product_description"] == "text"
115116
assert asset_type_map["impression_tracker"] == "url"
116117

117-
# Check optional assets are in assets but not in assets_required
118+
# Check optional assets are in assets but not required
118119
optional_asset_ids = {
119120
get_asset_attr(asset, "asset_id") for asset in fmt.assets if not get_asset_attr(asset, "required")
120121
}
@@ -149,12 +150,13 @@ def test_requires_product_assets(self):
149150
assert fmt.assets
150151
assert len(fmt.assets) == 9 # 8 original + impression_tracker
151152

152-
# Check required assets are in assets_required (backward compatibility)
153-
assert fmt.assets_required
154-
assert len(fmt.assets_required) == 3 # Only required=True assets
153+
# Check required assets
154+
required_assets = get_required_assets(fmt)
155+
assert required_assets
156+
assert len(required_assets) == 3 # Only required=True assets
155157

156-
# Check required asset ids in assets_required
157-
required_asset_ids = {get_asset_attr(asset, "asset_id") for asset in fmt.assets_required}
158+
# Check required asset ids
159+
required_asset_ids = {get_asset_attr(asset, "asset_id") for asset in required_assets}
158160
assert "product_image" in required_asset_ids
159161
assert "product_name" in required_asset_ids
160162
assert "product_description" in required_asset_ids
@@ -168,7 +170,7 @@ def test_requires_product_assets(self):
168170
assert asset_type_map["product_description"] == "text"
169171
assert asset_type_map["impression_tracker"] == "url"
170172

171-
# Check optional assets are in assets but not in assets_required
173+
# Check optional assets are in assets but not required
172174
optional_asset_ids = {
173175
get_asset_attr(asset, "asset_id") for asset in fmt.assets if not get_asset_attr(asset, "required")
174176
}
@@ -198,9 +200,10 @@ def test_requires_format_asset(self):
198200
format_id = FormatId(agent_url=AGENT_URL, id="format_card_standard")
199201
results = filter_formats(format_ids=[format_id])
200202
fmt = results[0]
201-
assert fmt.assets_required
202-
assert len(fmt.assets_required) == 1
203-
asset = fmt.assets_required[0]
203+
required_assets = get_required_assets(fmt)
204+
assert required_assets
205+
assert len(required_assets) == 1
206+
asset = required_assets[0]
204207
assert get_asset_attr(asset, "asset_id") == "format"
205208
assert get_asset_attr(asset, "asset_type") == "text"
206209
assert get_asset_attr(asset, "required") is True
@@ -228,9 +231,10 @@ def test_requires_format_asset(self):
228231
format_id = FormatId(agent_url=AGENT_URL, id="format_card_detailed")
229232
results = filter_formats(format_ids=[format_id])
230233
fmt = results[0]
231-
assert fmt.assets_required
232-
assert len(fmt.assets_required) == 1
233-
asset = fmt.assets_required[0]
234+
required_assets = get_required_assets(fmt)
235+
assert required_assets
236+
assert len(required_assets) == 1
237+
asset = required_assets[0]
234238
assert get_asset_attr(asset, "asset_id") == "format"
235239
assert get_asset_attr(asset, "asset_type") == "text"
236240
assert get_asset_attr(asset, "required") is True

tests/validation/test_template_parameter_validation.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"""
99

1010
import pytest
11-
from adcp import FormatId
11+
from adcp import FormatId, get_required_assets
1212
from adcp.types.generated_poc.enums.format_id_parameter import FormatIdParameter
1313
from pydantic import AnyUrl, ValidationError
1414

@@ -112,7 +112,7 @@ def test_extract_dimensions_from_format_id(self):
112112
fmt = get_format_by_id(template_format_id)
113113

114114
assert fmt is not None
115-
assert fmt.assets_required is not None
115+
assert get_required_assets(fmt) is not None
116116

117117
# Template format accepts dimensions parameter
118118
assert FormatIdParameter.dimensions in getattr(fmt, "accepts_parameters", [])
@@ -131,7 +131,7 @@ def test_extract_duration_from_format_id(self):
131131
fmt = get_format_by_id(template_format_id)
132132

133133
assert fmt is not None
134-
assert fmt.assets_required is not None
134+
assert get_required_assets(fmt) is not None
135135

136136
# Template format accepts duration parameter
137137
assert FormatIdParameter.duration in getattr(fmt, "accepts_parameters", [])
@@ -161,7 +161,7 @@ def test_concrete_format_has_explicit_dimensions(self):
161161
format_id = FormatId(agent_url=AnyUrl(str(AGENT_URL)), id="display_300x250_image")
162162

163163
fmt = get_format_by_id(format_id)
164-
image_asset = next(a for a in fmt.assets_required if a.asset_id == "banner_image")
164+
image_asset = next(a for a in get_required_assets(fmt) if a.asset_id == "banner_image")
165165

166166
requirements = getattr(image_asset, "requirements", {})
167167

uv.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)