Skip to content

Commit b71ec0a

Browse files
bokelleyclaude
andcommitted
refactor: change __str__ to .summary() method
Switches from overriding __str__ to an explicit .summary() method: - Avoids shadowing 'message' field in generated types (Error, etc.) - Keeps str() for Pydantic's default repr (better for debugging) - More explicit API: response.summary() vs str(response) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent e30abba commit b71ec0a

File tree

2 files changed

+71
-66
lines changed

2 files changed

+71
-66
lines changed

src/adcp/types/base.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -204,18 +204,16 @@ def model_dump_json(self, **kwargs: Any) -> str:
204204
kwargs["exclude_none"] = True
205205
return super().model_dump_json(**kwargs)
206206

207-
def __str__(self) -> str:
208-
"""Generate human-readable message for protocol responses.
207+
def summary(self) -> str:
208+
"""Human-readable summary for protocol responses.
209209
210-
For response types with registered formatters, returns a standardized
211-
human-readable message suitable for MCP tool results, A2A task
212-
communications, and REST API responses.
210+
Returns a standardized human-readable message suitable for MCP tool
211+
results, A2A task communications, and REST API responses.
213212
214-
For types without a registered formatter, falls back to Pydantic's
215-
default string representation.
213+
For types without a registered formatter, returns a generic message
214+
with the class name.
216215
"""
217-
cls_name = self.__class__.__name__
218-
formatter = _RESPONSE_MESSAGE_REGISTRY.get(cls_name)
216+
formatter = _RESPONSE_MESSAGE_REGISTRY.get(self.__class__.__name__)
219217
if formatter:
220218
return formatter(self)
221-
return super().__str__()
219+
return f"{self.__class__.__name__} response"

tests/test_response_str.py

Lines changed: 63 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Tests for __str__() methods on response types.
1+
"""Tests for .summary() method on response types.
22
33
These tests verify that response types return human-readable messages
44
suitable for MCP tool results, A2A task communications, and REST API responses.
@@ -30,15 +30,15 @@
3030
)
3131

3232

33-
class TestGetProductsResponseStr:
34-
"""Tests for GetProductsResponse.__str__()."""
33+
class TestGetProductsResponseMessage:
34+
"""Tests for GetProductsResponse.summary()."""
3535

3636
def test_singular_product(self):
3737
"""Single product uses singular form."""
3838
response = GetProductsResponse.model_construct(
3939
products=[{"product_id": "p1", "name": "Test"}]
4040
)
41-
assert str(response) == "Found 1 product matching your requirements."
41+
assert response.summary() == "Found 1 product matching your requirements."
4242

4343
def test_multiple_products(self):
4444
"""Multiple products uses plural form."""
@@ -49,23 +49,23 @@ def test_multiple_products(self):
4949
{"product_id": "p3", "name": "Test 3"},
5050
]
5151
)
52-
assert str(response) == "Found 3 products matching your requirements."
52+
assert response.summary() == "Found 3 products matching your requirements."
5353

5454
def test_zero_products(self):
5555
"""Zero products uses conversational message."""
5656
response = GetProductsResponse.model_construct(products=[])
57-
assert str(response) == "No products matched your requirements."
57+
assert response.summary() == "No products matched your requirements."
5858

5959

60-
class TestListCreativeFormatsResponseStr:
61-
"""Tests for ListCreativeFormatsResponse.__str__()."""
60+
class TestListCreativeFormatsResponseMessage:
61+
"""Tests for ListCreativeFormatsResponse.summary()."""
6262

6363
def test_singular_format(self):
6464
"""Single format uses singular form."""
6565
response = ListCreativeFormatsResponse.model_construct(
6666
formats=[{"format_id": "f1", "name": "Banner"}]
6767
)
68-
assert str(response) == "Found 1 supported creative format."
68+
assert response.summary() == "Found 1 supported creative format."
6969

7070
def test_multiple_formats(self):
7171
"""Multiple formats uses plural form."""
@@ -75,62 +75,62 @@ def test_multiple_formats(self):
7575
{"format_id": "f2", "name": "Banner 2"},
7676
]
7777
)
78-
assert str(response) == "Found 2 supported creative formats."
78+
assert response.summary() == "Found 2 supported creative formats."
7979

8080

81-
class TestGetSignalsResponseStr:
82-
"""Tests for GetSignalsResponse.__str__()."""
81+
class TestGetSignalsResponseMessage:
82+
"""Tests for GetSignalsResponse.summary()."""
8383

8484
def test_singular_signal(self):
8585
"""Single signal uses singular form."""
8686
response = GetSignalsResponse.model_construct(signals=[{"signal_id": "s1"}])
87-
assert str(response) == "Found 1 signal available for targeting."
87+
assert response.summary() == "Found 1 signal available for targeting."
8888

8989
def test_multiple_signals(self):
9090
"""Multiple signals uses plural form."""
9191
response = GetSignalsResponse.model_construct(
9292
signals=[{"signal_id": "s1"}, {"signal_id": "s2"}]
9393
)
94-
assert str(response) == "Found 2 signals available for targeting."
94+
assert response.summary() == "Found 2 signals available for targeting."
9595

9696

97-
class TestListAuthorizedPropertiesResponseStr:
98-
"""Tests for ListAuthorizedPropertiesResponse.__str__()."""
97+
class TestListAuthorizedPropertiesResponseMessage:
98+
"""Tests for ListAuthorizedPropertiesResponse.summary()."""
9999

100100
def test_singular_domain(self):
101101
"""Single domain uses singular form."""
102102
response = ListAuthorizedPropertiesResponse.model_construct(
103103
publisher_domains=["example.com"]
104104
)
105-
assert str(response) == "Authorized to represent 1 publisher domain."
105+
assert response.summary() == "Authorized to represent 1 publisher domain."
106106

107107
def test_multiple_domains(self):
108108
"""Multiple domains uses plural form."""
109109
response = ListAuthorizedPropertiesResponse.model_construct(
110110
publisher_domains=["example.com", "test.com", "demo.com"]
111111
)
112-
assert str(response) == "Authorized to represent 3 publisher domains."
112+
assert response.summary() == "Authorized to represent 3 publisher domains."
113113

114114

115-
class TestListCreativesResponseStr:
116-
"""Tests for ListCreativesResponse.__str__()."""
115+
class TestListCreativesResponseMessage:
116+
"""Tests for ListCreativesResponse.summary()."""
117117

118118
def test_singular_creative(self):
119119
"""Single creative uses singular form."""
120120
response = ListCreativesResponse.model_construct(
121121
creatives=[{"creative_id": "c1"}]
122122
)
123-
assert str(response) == "Found 1 creative in the system."
123+
assert response.summary() == "Found 1 creative in the system."
124124

125125
def test_multiple_creatives(self):
126126
"""Multiple creatives uses plural form."""
127127
response = ListCreativesResponse.model_construct(
128128
creatives=[{"creative_id": "c1"}, {"creative_id": "c2"}]
129129
)
130-
assert str(response) == "Found 2 creatives in the system."
130+
assert response.summary() == "Found 2 creatives in the system."
131131

132132

133-
class TestCreateMediaBuyResponseStr:
133+
class TestCreateMediaBuyResponseMessage:
134134
"""Tests for CreateMediaBuyResponse success/error variants."""
135135

136136
def test_success_singular_package(self):
@@ -140,7 +140,7 @@ def test_success_singular_package(self):
140140
buyer_ref="ref_456",
141141
packages=[{"package_id": "pkg_1"}],
142142
)
143-
assert str(response) == "Media buy mb_123 created with 1 package."
143+
assert response.summary() == "Media buy mb_123 created with 1 package."
144144

145145
def test_success_multiple_packages(self):
146146
"""Success with multiple packages."""
@@ -149,14 +149,14 @@ def test_success_multiple_packages(self):
149149
buyer_ref="ref_789",
150150
packages=[{"package_id": "pkg_1"}, {"package_id": "pkg_2"}],
151151
)
152-
assert str(response) == "Media buy mb_456 created with 2 packages."
152+
assert response.summary() == "Media buy mb_456 created with 2 packages."
153153

154154
def test_error_singular(self):
155155
"""Error with single error."""
156156
response = CreateMediaBuyResponse2.model_construct(
157157
errors=[{"code": "invalid", "message": "Failed"}]
158158
)
159-
assert str(response) == "Media buy creation failed with 1 error."
159+
assert response.summary() == "Media buy creation failed with 1 error."
160160

161161
def test_error_multiple(self):
162162
"""Error with multiple errors."""
@@ -166,10 +166,10 @@ def test_error_multiple(self):
166166
{"code": "invalid", "message": "Error 2"},
167167
]
168168
)
169-
assert str(response) == "Media buy creation failed with 2 errors."
169+
assert response.summary() == "Media buy creation failed with 2 errors."
170170

171171

172-
class TestUpdateMediaBuyResponseStr:
172+
class TestUpdateMediaBuyResponseMessage:
173173
"""Tests for UpdateMediaBuyResponse success/error variants."""
174174

175175
def test_success(self):
@@ -178,25 +178,25 @@ def test_success(self):
178178
media_buy_id="mb_789",
179179
packages=[],
180180
)
181-
assert str(response) == "Media buy mb_789 updated successfully."
181+
assert response.summary() == "Media buy mb_789 updated successfully."
182182

183183
def test_error(self):
184184
"""Error message includes error count."""
185185
response = UpdateMediaBuyResponse2.model_construct(
186186
errors=[{"code": "not_found", "message": "Not found"}]
187187
)
188-
assert str(response) == "Media buy update failed with 1 error."
188+
assert response.summary() == "Media buy update failed with 1 error."
189189

190190

191-
class TestSyncCreativesResponseStr:
191+
class TestSyncCreativesResponseMessage:
192192
"""Tests for SyncCreativesResponse success/error variants."""
193193

194194
def test_success_singular(self):
195195
"""Success with single creative synced."""
196196
response = SyncCreativesResponse1.model_construct(
197197
creatives=[{"creative_id": "c1", "action": "created"}]
198198
)
199-
assert str(response) == "Synced 1 creative successfully."
199+
assert response.summary() == "Synced 1 creative successfully."
200200

201201
def test_success_multiple(self):
202202
"""Success with multiple creatives synced."""
@@ -207,35 +207,35 @@ def test_success_multiple(self):
207207
{"creative_id": "c3", "action": "created"},
208208
]
209209
)
210-
assert str(response) == "Synced 3 creatives successfully."
210+
assert response.summary() == "Synced 3 creatives successfully."
211211

212212
def test_error(self):
213213
"""Error message includes error count."""
214214
response = SyncCreativesResponse2.model_construct(
215215
errors=[{"code": "sync_failed", "message": "Failed"}]
216216
)
217-
assert str(response) == "Creative sync failed with 1 error."
217+
assert response.summary() == "Creative sync failed with 1 error."
218218

219219

220-
class TestActivateSignalResponseStr:
220+
class TestActivateSignalResponseMessage:
221221
"""Tests for ActivateSignalResponse success/error variants."""
222222

223223
def test_success(self):
224224
"""Success message is simple confirmation."""
225225
response = ActivateSignalResponse1.model_construct(
226226
activation_status="active"
227227
)
228-
assert str(response) == "Signal activated successfully."
228+
assert response.summary() == "Signal activated successfully."
229229

230230
def test_error(self):
231231
"""Error message includes error count."""
232232
response = ActivateSignalResponse2.model_construct(
233233
errors=[{"code": "activation_failed", "message": "Failed"}]
234234
)
235-
assert str(response) == "Signal activation failed with 1 error."
235+
assert response.summary() == "Signal activation failed with 1 error."
236236

237237

238-
class TestPreviewCreativeResponseStr:
238+
class TestPreviewCreativeResponseMessage:
239239
"""Tests for PreviewCreativeResponse single/batch variants."""
240240

241241
def test_single_singular(self):
@@ -245,7 +245,7 @@ def test_single_singular(self):
245245
expires_at="2025-12-01T00:00:00Z",
246246
previews=[{"preview_id": "p1"}],
247247
)
248-
assert str(response) == "Generated 1 preview."
248+
assert response.summary() == "Generated 1 preview."
249249

250250
def test_single_multiple(self):
251251
"""Single request with multiple previews."""
@@ -254,52 +254,52 @@ def test_single_multiple(self):
254254
expires_at="2025-12-01T00:00:00Z",
255255
previews=[{"preview_id": "p1"}, {"preview_id": "p2"}],
256256
)
257-
assert str(response) == "Generated 2 previews."
257+
assert response.summary() == "Generated 2 previews."
258258

259259
def test_batch_singular(self):
260260
"""Batch request with one manifest."""
261261
response = PreviewCreativeResponse2.model_construct(
262262
response_type="batch",
263263
results=[{"manifest_id": "m1"}],
264264
)
265-
assert str(response) == "Generated previews for 1 manifest."
265+
assert response.summary() == "Generated previews for 1 manifest."
266266

267267
def test_batch_multiple(self):
268268
"""Batch request with multiple manifests."""
269269
response = PreviewCreativeResponse2.model_construct(
270270
response_type="batch",
271271
results=[{"manifest_id": "m1"}, {"manifest_id": "m2"}],
272272
)
273-
assert str(response) == "Generated previews for 2 manifests."
273+
assert response.summary() == "Generated previews for 2 manifests."
274274

275275

276-
class TestBuildCreativeResponseStr:
276+
class TestBuildCreativeResponseMessage:
277277
"""Tests for BuildCreativeResponse success/error variants."""
278278

279279
def test_success(self):
280280
"""Success message is simple confirmation."""
281281
response = BuildCreativeResponse1.model_construct(
282282
assets=[{"url": "https://example.com/asset"}]
283283
)
284-
assert str(response) == "Creative built successfully."
284+
assert response.summary() == "Creative built successfully."
285285

286286
def test_error(self):
287287
"""Error message includes error count."""
288288
response = BuildCreativeResponse2.model_construct(
289289
errors=[{"code": "build_failed", "message": "Failed"}]
290290
)
291-
assert str(response) == "Creative build failed with 1 error."
291+
assert response.summary() == "Creative build failed with 1 error."
292292

293293

294-
class TestGetMediaBuyDeliveryResponseStr:
295-
"""Tests for GetMediaBuyDeliveryResponse.__str__()."""
294+
class TestGetMediaBuyDeliveryResponseMessage:
295+
"""Tests for GetMediaBuyDeliveryResponse.summary()."""
296296

297297
def test_with_single_media_buy(self):
298298
"""Response with single media buy delivery data."""
299299
response = GetMediaBuyDeliveryResponse.model_construct(
300300
media_buy_deliveries=[{"media_buy_id": "mb_123"}]
301301
)
302-
assert str(response) == "Retrieved delivery data for 1 media buy."
302+
assert response.summary() == "Retrieved delivery data for 1 media buy."
303303

304304
def test_with_multiple_media_buys(self):
305305
"""Response with multiple media buy delivery data."""
@@ -309,32 +309,39 @@ def test_with_multiple_media_buys(self):
309309
{"media_buy_id": "mb_456"},
310310
]
311311
)
312-
assert str(response) == "Retrieved delivery data for 2 media buys."
312+
assert response.summary() == "Retrieved delivery data for 2 media buys."
313313

314314

315-
class TestProvidePerformanceFeedbackResponseStr:
315+
class TestProvidePerformanceFeedbackResponseMessage:
316316
"""Tests for ProvidePerformanceFeedbackResponse success/error variants."""
317317

318318
def test_success(self):
319319
"""Success message is simple confirmation."""
320320
response = ProvidePerformanceFeedbackResponse1.model_construct(
321321
acknowledged=True
322322
)
323-
assert str(response) == "Performance feedback recorded successfully."
323+
assert response.summary() == "Performance feedback recorded successfully."
324324

325325
def test_error(self):
326326
"""Error message includes error count."""
327327
response = ProvidePerformanceFeedbackResponse2.model_construct(
328328
errors=[{"code": "feedback_failed", "message": "Failed"}]
329329
)
330-
assert str(response) == "Performance feedback recording failed with 1 error."
330+
assert response.summary() == "Performance feedback recording failed with 1 error."
331331

332332

333-
class TestNonResponseTypeStr:
334-
"""Tests for __str__ on non-response types."""
333+
class TestNonResponseTypeMessage:
334+
"""Tests for .summary() on non-response types."""
335335

336-
def test_request_type_falls_back_to_default(self):
337-
"""Request types fall back to Pydantic's default __str__."""
336+
def test_request_type_returns_generic_message(self):
337+
"""Request types return generic message with class name."""
338+
from adcp.types import GetProductsRequest
339+
340+
request = GetProductsRequest(brief="Test brief")
341+
assert request.summary() == "GetProductsRequest response"
342+
343+
def test_str_returns_pydantic_default(self):
344+
"""str() returns Pydantic's default representation for inspection."""
338345
from adcp.types import GetProductsRequest
339346

340347
request = GetProductsRequest(brief="Test brief")

0 commit comments

Comments
 (0)