adcp 2.15.0__py3-none-any.whl → 2.17.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/__init__.py CHANGED
@@ -186,7 +186,7 @@ from adcp.webhooks import (
186
186
  get_adcp_signed_headers_for_webhook,
187
187
  )
188
188
 
189
- __version__ = "2.15.0"
189
+ __version__ = "2.17.0"
190
190
 
191
191
 
192
192
  def get_adcp_version() -> str:
adcp/types/_ergonomic.py CHANGED
@@ -44,7 +44,11 @@ from adcp.types.coercion import (
44
44
  from adcp.types.generated_poc.core.context import ContextObject
45
45
  from adcp.types.generated_poc.core.creative_asset import CreativeAsset
46
46
  from adcp.types.generated_poc.core.creative_assignment import CreativeAssignment
47
+ from adcp.types.generated_poc.core.error import Error
47
48
  from adcp.types.generated_poc.core.ext import ExtensionObject
49
+ from adcp.types.generated_poc.core.format import Format
50
+ from adcp.types.generated_poc.core.package import Package
51
+ from adcp.types.generated_poc.core.product import Product
48
52
  from adcp.types.generated_poc.enums.asset_content_type import AssetContentType
49
53
  from adcp.types.generated_poc.enums.creative_sort_field import CreativeSortField
50
54
  from adcp.types.generated_poc.enums.format_category import FormatCategory
@@ -53,15 +57,34 @@ from adcp.types.generated_poc.enums.sort_direction import SortDirection
53
57
  from adcp.types.generated_poc.media_buy.create_media_buy_request import (
54
58
  CreateMediaBuyRequest,
55
59
  )
60
+
61
+ # Response types
62
+ from adcp.types.generated_poc.media_buy.create_media_buy_response import (
63
+ CreateMediaBuyResponse1,
64
+ )
65
+ from adcp.types.generated_poc.media_buy.get_media_buy_delivery_response import (
66
+ GetMediaBuyDeliveryResponse,
67
+ MediaBuyDelivery,
68
+ NotificationType,
69
+ )
56
70
  from adcp.types.generated_poc.media_buy.get_products_request import GetProductsRequest
71
+ from adcp.types.generated_poc.media_buy.get_products_response import GetProductsResponse
57
72
  from adcp.types.generated_poc.media_buy.list_creative_formats_request import (
58
73
  ListCreativeFormatsRequest,
59
74
  )
75
+ from adcp.types.generated_poc.media_buy.list_creative_formats_response import (
76
+ CreativeAgent,
77
+ ListCreativeFormatsResponse,
78
+ )
60
79
  from adcp.types.generated_poc.media_buy.list_creatives_request import (
61
80
  FieldModel,
62
81
  ListCreativesRequest,
63
82
  Sort,
64
83
  )
84
+ from adcp.types.generated_poc.media_buy.list_creatives_response import (
85
+ Creative,
86
+ ListCreativesResponse,
87
+ )
65
88
  from adcp.types.generated_poc.media_buy.package_request import PackageRequest
66
89
  from adcp.types.generated_poc.media_buy.update_media_buy_request import (
67
90
  Packages,
@@ -261,6 +284,168 @@ def _apply_coercion() -> None:
261
284
  )
262
285
  Packages1.model_rebuild(force=True)
263
286
 
287
+ # Apply coercion to GetProductsResponse
288
+ # - context: ContextObject | dict | None
289
+ # - errors: list[Error] (accepts subclass instances)
290
+ # - ext: ExtensionObject | dict | None
291
+ # - products: list[Product] (accepts subclass instances)
292
+ _patch_field_annotation(
293
+ GetProductsResponse,
294
+ "context",
295
+ Annotated[ContextObject | None, BeforeValidator(coerce_to_model(ContextObject))],
296
+ )
297
+ _patch_field_annotation(
298
+ GetProductsResponse,
299
+ "errors",
300
+ Annotated[
301
+ list[Error] | None,
302
+ BeforeValidator(coerce_subclass_list(Error)),
303
+ ],
304
+ )
305
+ _patch_field_annotation(
306
+ GetProductsResponse,
307
+ "ext",
308
+ Annotated[ExtensionObject | None, BeforeValidator(coerce_to_model(ExtensionObject))],
309
+ )
310
+ _patch_field_annotation(
311
+ GetProductsResponse,
312
+ "products",
313
+ Annotated[
314
+ list[Product],
315
+ BeforeValidator(coerce_subclass_list(Product)),
316
+ ],
317
+ )
318
+ GetProductsResponse.model_rebuild(force=True)
319
+
320
+ # Apply coercion to ListCreativesResponse
321
+ # - context: ContextObject | dict | None
322
+ # - creatives: list[Creative] (accepts subclass instances)
323
+ # - ext: ExtensionObject | dict | None
324
+ _patch_field_annotation(
325
+ ListCreativesResponse,
326
+ "context",
327
+ Annotated[ContextObject | None, BeforeValidator(coerce_to_model(ContextObject))],
328
+ )
329
+ _patch_field_annotation(
330
+ ListCreativesResponse,
331
+ "creatives",
332
+ Annotated[
333
+ list[Creative],
334
+ BeforeValidator(coerce_subclass_list(Creative)),
335
+ ],
336
+ )
337
+ _patch_field_annotation(
338
+ ListCreativesResponse,
339
+ "ext",
340
+ Annotated[ExtensionObject | None, BeforeValidator(coerce_to_model(ExtensionObject))],
341
+ )
342
+ ListCreativesResponse.model_rebuild(force=True)
343
+
344
+ # Apply coercion to ListCreativeFormatsResponse
345
+ # - context: ContextObject | dict | None
346
+ # - creative_agents: list[CreativeAgent] (accepts subclass instances)
347
+ # - errors: list[Error] (accepts subclass instances)
348
+ # - ext: ExtensionObject | dict | None
349
+ # - formats: list[Format] (accepts subclass instances)
350
+ _patch_field_annotation(
351
+ ListCreativeFormatsResponse,
352
+ "context",
353
+ Annotated[ContextObject | None, BeforeValidator(coerce_to_model(ContextObject))],
354
+ )
355
+ _patch_field_annotation(
356
+ ListCreativeFormatsResponse,
357
+ "creative_agents",
358
+ Annotated[
359
+ list[CreativeAgent] | None,
360
+ BeforeValidator(coerce_subclass_list(CreativeAgent)),
361
+ ],
362
+ )
363
+ _patch_field_annotation(
364
+ ListCreativeFormatsResponse,
365
+ "errors",
366
+ Annotated[
367
+ list[Error] | None,
368
+ BeforeValidator(coerce_subclass_list(Error)),
369
+ ],
370
+ )
371
+ _patch_field_annotation(
372
+ ListCreativeFormatsResponse,
373
+ "ext",
374
+ Annotated[ExtensionObject | None, BeforeValidator(coerce_to_model(ExtensionObject))],
375
+ )
376
+ _patch_field_annotation(
377
+ ListCreativeFormatsResponse,
378
+ "formats",
379
+ Annotated[
380
+ list[Format],
381
+ BeforeValidator(coerce_subclass_list(Format)),
382
+ ],
383
+ )
384
+ ListCreativeFormatsResponse.model_rebuild(force=True)
385
+
386
+ # Apply coercion to CreateMediaBuyResponse1
387
+ # - context: ContextObject | dict | None
388
+ # - ext: ExtensionObject | dict | None
389
+ # - packages: list[Package] (accepts subclass instances)
390
+ _patch_field_annotation(
391
+ CreateMediaBuyResponse1,
392
+ "context",
393
+ Annotated[ContextObject | None, BeforeValidator(coerce_to_model(ContextObject))],
394
+ )
395
+ _patch_field_annotation(
396
+ CreateMediaBuyResponse1,
397
+ "ext",
398
+ Annotated[ExtensionObject | None, BeforeValidator(coerce_to_model(ExtensionObject))],
399
+ )
400
+ _patch_field_annotation(
401
+ CreateMediaBuyResponse1,
402
+ "packages",
403
+ Annotated[
404
+ list[Package],
405
+ BeforeValidator(coerce_subclass_list(Package)),
406
+ ],
407
+ )
408
+ CreateMediaBuyResponse1.model_rebuild(force=True)
409
+
410
+ # Apply coercion to GetMediaBuyDeliveryResponse
411
+ # - context: ContextObject | dict | None
412
+ # - errors: list[Error] (accepts subclass instances)
413
+ # - ext: ExtensionObject | dict | None
414
+ # - media_buy_deliveries: list[MediaBuyDelivery] (accepts subclass instances)
415
+ # - notification_type: NotificationType | str | None
416
+ _patch_field_annotation(
417
+ GetMediaBuyDeliveryResponse,
418
+ "context",
419
+ Annotated[ContextObject | None, BeforeValidator(coerce_to_model(ContextObject))],
420
+ )
421
+ _patch_field_annotation(
422
+ GetMediaBuyDeliveryResponse,
423
+ "errors",
424
+ Annotated[
425
+ list[Error] | None,
426
+ BeforeValidator(coerce_subclass_list(Error)),
427
+ ],
428
+ )
429
+ _patch_field_annotation(
430
+ GetMediaBuyDeliveryResponse,
431
+ "ext",
432
+ Annotated[ExtensionObject | None, BeforeValidator(coerce_to_model(ExtensionObject))],
433
+ )
434
+ _patch_field_annotation(
435
+ GetMediaBuyDeliveryResponse,
436
+ "media_buy_deliveries",
437
+ Annotated[
438
+ list[MediaBuyDelivery],
439
+ BeforeValidator(coerce_subclass_list(MediaBuyDelivery)),
440
+ ],
441
+ )
442
+ _patch_field_annotation(
443
+ GetMediaBuyDeliveryResponse,
444
+ "notification_type",
445
+ Annotated[NotificationType | None, BeforeValidator(coerce_to_enum(NotificationType))],
446
+ )
447
+ GetMediaBuyDeliveryResponse.model_rebuild(force=True)
448
+
264
449
 
265
450
  def _patch_field_annotation(
266
451
  model: type,
@@ -129,12 +129,67 @@ def parse_mcp_content(content: list[dict[str, Any]], response_type: type[T]) ->
129
129
  )
130
130
 
131
131
 
132
+ # Protocol-level fields from ProtocolResponse (core/response.json) and
133
+ # ProtocolEnvelope (core/protocol_envelope.json). These are separated from
134
+ # task data for schema validation, but preserved at the TaskResult level.
135
+ # Note: 'data' and 'payload' are handled separately as wrapper fields.
136
+ PROTOCOL_FIELDS = {
137
+ "message", # Human-readable summary
138
+ "context_id", # Session continuity identifier
139
+ "task_id", # Async operation identifier
140
+ "status", # Task execution state
141
+ "timestamp", # Response timestamp
142
+ }
143
+
144
+
145
+ def _extract_task_data(data: dict[str, Any]) -> dict[str, Any]:
146
+ """
147
+ Extract task-specific data from a protocol response.
148
+
149
+ Servers may return responses in ProtocolResponse format:
150
+ {"message": "...", "context_id": "...", "data": {...}}
151
+
152
+ Or ProtocolEnvelope format:
153
+ {"message": "...", "status": "...", "payload": {...}}
154
+
155
+ Or task data directly with protocol fields mixed in:
156
+ {"message": "...", "products": [...], ...}
157
+
158
+ This function separates task-specific data for schema validation.
159
+ Protocol fields are preserved at the TaskResult level.
160
+
161
+ Args:
162
+ data: Response data dict
163
+
164
+ Returns:
165
+ Task-specific data suitable for schema validation.
166
+ Returns the same dict object if no extraction is needed.
167
+ """
168
+ # Check for wrapped payload fields
169
+ # (ProtocolResponse uses 'data', ProtocolEnvelope uses 'payload')
170
+ if "data" in data and isinstance(data["data"], dict):
171
+ return data["data"]
172
+ if "payload" in data and isinstance(data["payload"], dict):
173
+ return data["payload"]
174
+
175
+ # Check if any protocol fields are present
176
+ if not any(k in PROTOCOL_FIELDS for k in data):
177
+ return data # Return same object for identity check
178
+
179
+ # Separate task data from protocol fields
180
+ return {k: v for k, v in data.items() if k not in PROTOCOL_FIELDS}
181
+
182
+
132
183
  def parse_json_or_text(data: Any, response_type: type[T]) -> T:
133
184
  """
134
185
  Parse data that might be JSON string, dict, or other format.
135
186
 
136
187
  Used by A2A adapter for flexible response parsing.
137
188
 
189
+ Handles protocol-level wrapping where servers return:
190
+ - {"message": "...", "data": {...task_data...}}
191
+ - {"message": "...", ...task_fields...}
192
+
138
193
  Args:
139
194
  data: Response data (string, dict, or other)
140
195
  response_type: Expected Pydantic model type
@@ -147,22 +202,42 @@ def parse_json_or_text(data: Any, response_type: type[T]) -> T:
147
202
  """
148
203
  # If already a dict, try direct validation
149
204
  if isinstance(data, dict):
205
+ # Try direct validation first
206
+ original_error: Exception | None = None
150
207
  try:
151
208
  return _validate_union_type(data, response_type)
152
- except ValidationError as e:
153
- # Get the type name, handling Union types
154
- type_name = getattr(response_type, "__name__", str(response_type))
155
- raise ValueError(f"Response doesn't match expected schema {type_name}: {e}") from e
209
+ except (ValidationError, ValueError) as e:
210
+ original_error = e
211
+
212
+ # Try extracting task data (separates protocol fields)
213
+ task_data = _extract_task_data(data)
214
+ if task_data is not data:
215
+ try:
216
+ return _validate_union_type(task_data, response_type)
217
+ except (ValidationError, ValueError):
218
+ pass # Fall through to raise original error
219
+
220
+ # Report the original validation error
221
+ type_name = getattr(response_type, "__name__", str(response_type))
222
+ raise ValueError(
223
+ f"Response doesn't match expected schema {type_name}: {original_error}"
224
+ ) from original_error
156
225
 
157
226
  # If string, try JSON parsing
158
227
  if isinstance(data, str):
159
228
  try:
160
229
  parsed = json.loads(data)
161
- return _validate_union_type(parsed, response_type)
162
230
  except json.JSONDecodeError as e:
163
231
  raise ValueError(f"Response is not valid JSON: {e}") from e
232
+
233
+ # Recursively handle dict parsing (which includes protocol field extraction)
234
+ if isinstance(parsed, dict):
235
+ return parse_json_or_text(parsed, response_type)
236
+
237
+ # Non-dict JSON (shouldn't happen for AdCP responses)
238
+ try:
239
+ return _validate_union_type(parsed, response_type)
164
240
  except ValidationError as e:
165
- # Get the type name, handling Union types
166
241
  type_name = getattr(response_type, "__name__", str(response_type))
167
242
  raise ValueError(f"Response doesn't match expected schema {type_name}: {e}") from e
168
243
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adcp
3
- Version: 2.15.0
3
+ Version: 2.17.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
@@ -1,5 +1,5 @@
1
1
  adcp/ADCP_VERSION,sha256=cy9k2HT5B4jAGZsMGb_Zs7ZDbhQBFOU-RigyUy10xhw,6
2
- adcp/__init__.py,sha256=610aiJX_vK9RLLzrn6jclEGc9Ux2ExbdflZvDcC3Tms,10319
2
+ adcp/__init__.py,sha256=0ihzp5E5L6yBTaALFz3HKjSSN3eTb_rY3EAJTMMVYH4,10319
3
3
  adcp/__main__.py,sha256=8v-j_W9IIWDIcvhaR1yuZAhBCDRfehUyN0tusstzyfQ,15139
4
4
  adcp/adagents.py,sha256=HY7vZ8QqC8Zjzc_jRA0ZMhAfvm1pJN_VJfS8a8Fbc6c,24481
5
5
  adcp/client.py,sha256=gdDnyCyotRKMBHgxyONq3BWr4Seo7U2KZE18gqpw3nU,50839
@@ -16,7 +16,7 @@ adcp/protocols/mcp.py,sha256=kCKRDhnahRI5nCE9p7FmhrayfMcKUe0h6uYbuSZ4SDE,22642
16
16
  adcp/testing/__init__.py,sha256=ZWp_floWjVZfy8RBG5v_FUXQ8YbN7xjXvVcX-_zl_HU,1416
17
17
  adcp/testing/test_helpers.py,sha256=-UKuxxyKQald5EvXxguQH34b3J0JdsxKH_nRT6GTjkQ,10029
18
18
  adcp/types/__init__.py,sha256=9f_6R333b2rGA6nXHk4FeVw40nh8bOcCQ7iasAgd8aA,13746
19
- adcp/types/_ergonomic.py,sha256=TOlRTuOrH4-UUcoXUdZ2al84KakRGmkI8DIAcu72ero,9659
19
+ adcp/types/_ergonomic.py,sha256=EYC4ltYIOGgZ8_f_eEecanr-uLyQgq8d2yAZnsnsoQU,15979
20
20
  adcp/types/_generated.py,sha256=Nd6vMiIJ8m0wRbD79dY5XTFmh0LbHBlvHIkLGQw0u7I,23268
21
21
  adcp/types/aliases.py,sha256=DDc-AwFJfaDce84Mkq57KFIPgfK1knoiA_OOkrmDN7A,26114
22
22
  adcp/types/base.py,sha256=Xr0cwbjG4tRPLbDTX9lucGty6_Y_S0a5C_ve0n7umc8,8227
@@ -185,10 +185,10 @@ adcp/types/generated_poc/signals/get_signals_response.py,sha256=F-JQXrXg3jXBZfm-
185
185
  adcp/utils/__init__.py,sha256=uetvSJB19CjQbtwEYZiTnumJG11GsafQmXm5eR3hL7E,153
186
186
  adcp/utils/operation_id.py,sha256=wQX9Bb5epXzRq23xoeYPTqzu5yLuhshg7lKJZihcM2k,294
187
187
  adcp/utils/preview_cache.py,sha256=fy792IGXX9385FGsOhyuN1ZjoagsCstmcC1a8HAwtlI,18771
188
- adcp/utils/response_parser.py,sha256=uPk2vIH-RYZmq7y3i8lC4HTMQ3FfKdlgXKTjgJ1955M,6253
189
- adcp-2.15.0.dist-info/licenses/LICENSE,sha256=PF39NR3Ae8PLgBhg3Uxw6ju7iGVIf8hfv9LRWQdii_U,629
190
- adcp-2.15.0.dist-info/METADATA,sha256=CISSCPjWx0UVGkgxgGHI2HRLxrgp-3PuXiGA5sTGgsc,31359
191
- adcp-2.15.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
192
- adcp-2.15.0.dist-info/entry_points.txt,sha256=DQKpcGsJX8DtVI_SGApQ7tNvqUB4zkTLaTAEpFgmi3U,44
193
- adcp-2.15.0.dist-info/top_level.txt,sha256=T1_NF0GefncFU9v_k56oDwKSJREyCqIM8lAwNZf0EOs,5
194
- adcp-2.15.0.dist-info/RECORD,,
188
+ adcp/utils/response_parser.py,sha256=WBYq8bZpPiVrG70PNhHDyE5_NyexS1qlsA8YRkxpZaQ,8986
189
+ adcp-2.17.0.dist-info/licenses/LICENSE,sha256=PF39NR3Ae8PLgBhg3Uxw6ju7iGVIf8hfv9LRWQdii_U,629
190
+ adcp-2.17.0.dist-info/METADATA,sha256=3ZsiYib0KMw4oolH1jg9KsGsUV8MgJ-oc-ysyox6Rjg,31359
191
+ adcp-2.17.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
192
+ adcp-2.17.0.dist-info/entry_points.txt,sha256=DQKpcGsJX8DtVI_SGApQ7tNvqUB4zkTLaTAEpFgmi3U,44
193
+ adcp-2.17.0.dist-info/top_level.txt,sha256=T1_NF0GefncFU9v_k56oDwKSJREyCqIM8lAwNZf0EOs,5
194
+ adcp-2.17.0.dist-info/RECORD,,
File without changes