adcp 2.16.0__tar.gz → 2.17.0__tar.gz

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 (217) hide show
  1. {adcp-2.16.0/src/adcp.egg-info → adcp-2.17.0}/PKG-INFO +1 -1
  2. {adcp-2.16.0 → adcp-2.17.0}/pyproject.toml +1 -1
  3. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/__init__.py +1 -1
  4. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/utils/response_parser.py +81 -6
  5. {adcp-2.16.0 → adcp-2.17.0/src/adcp.egg-info}/PKG-INFO +1 -1
  6. adcp-2.17.0/tests/test_response_parser.py +285 -0
  7. adcp-2.16.0/tests/test_response_parser.py +0 -137
  8. {adcp-2.16.0 → adcp-2.17.0}/LICENSE +0 -0
  9. {adcp-2.16.0 → adcp-2.17.0}/MANIFEST.in +0 -0
  10. {adcp-2.16.0 → adcp-2.17.0}/README.md +0 -0
  11. {adcp-2.16.0 → adcp-2.17.0}/setup.cfg +0 -0
  12. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/ADCP_VERSION +0 -0
  13. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/__main__.py +0 -0
  14. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/adagents.py +0 -0
  15. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/client.py +0 -0
  16. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/config.py +0 -0
  17. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/exceptions.py +0 -0
  18. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/protocols/__init__.py +0 -0
  19. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/protocols/a2a.py +0 -0
  20. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/protocols/base.py +0 -0
  21. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/protocols/mcp.py +0 -0
  22. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/py.typed +0 -0
  23. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/simple.py +0 -0
  24. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/testing/__init__.py +0 -0
  25. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/testing/test_helpers.py +0 -0
  26. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/__init__.py +0 -0
  27. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/_ergonomic.py +0 -0
  28. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/_generated.py +0 -0
  29. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/aliases.py +0 -0
  30. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/base.py +0 -0
  31. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/coercion.py +0 -0
  32. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/core.py +0 -0
  33. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/__init__.py +0 -0
  34. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/adagents.py +0 -0
  35. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/__init__.py +0 -0
  36. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/activation_key.py +0 -0
  37. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/__init__.py +0 -0
  38. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/audio_asset.py +0 -0
  39. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/css_asset.py +0 -0
  40. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/daast_asset.py +0 -0
  41. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/html_asset.py +0 -0
  42. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/image_asset.py +0 -0
  43. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/javascript_asset.py +0 -0
  44. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/text_asset.py +0 -0
  45. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/url_asset.py +0 -0
  46. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/vast_asset.py +0 -0
  47. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/video_asset.py +0 -0
  48. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/webhook_asset.py +0 -0
  49. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/async_response_data.py +0 -0
  50. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/brand_manifest.py +0 -0
  51. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/brand_manifest_ref.py +0 -0
  52. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/context.py +0 -0
  53. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/creative_asset.py +0 -0
  54. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/creative_assignment.py +0 -0
  55. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/creative_filters.py +0 -0
  56. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/creative_manifest.py +0 -0
  57. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/creative_policy.py +0 -0
  58. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/delivery_metrics.py +0 -0
  59. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/deployment.py +0 -0
  60. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/destination.py +0 -0
  61. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/error.py +0 -0
  62. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/ext.py +0 -0
  63. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/format.py +0 -0
  64. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/format_id.py +0 -0
  65. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/frequency_cap.py +0 -0
  66. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/mcp_webhook_payload.py +0 -0
  67. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/measurement.py +0 -0
  68. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/media_buy.py +0 -0
  69. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/package.py +0 -0
  70. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/performance_feedback.py +0 -0
  71. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/placement.py +0 -0
  72. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/pricing_option.py +0 -0
  73. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/product.py +0 -0
  74. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/product_filters.py +0 -0
  75. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/promoted_offerings.py +0 -0
  76. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/promoted_products.py +0 -0
  77. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/property.py +0 -0
  78. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/property_id.py +0 -0
  79. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/property_tag.py +0 -0
  80. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/protocol_envelope.py +0 -0
  81. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/publisher_property_selector.py +0 -0
  82. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/push_notification_config.py +0 -0
  83. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/reporting_capabilities.py +0 -0
  84. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/response.py +0 -0
  85. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/signal_filters.py +0 -0
  86. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/start_timing.py +0 -0
  87. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/sub_asset.py +0 -0
  88. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/targeting.py +0 -0
  89. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/creative/__init__.py +0 -0
  90. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/creative/list_creative_formats_request.py +0 -0
  91. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/creative/list_creative_formats_response.py +0 -0
  92. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/creative/preview_creative_request.py +0 -0
  93. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/creative/preview_creative_response.py +0 -0
  94. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/creative/preview_render.py +0 -0
  95. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/__init__.py +0 -0
  96. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/adcp_domain.py +0 -0
  97. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/asset_content_type.py +0 -0
  98. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/auth_scheme.py +0 -0
  99. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/available_metric.py +0 -0
  100. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/channels.py +0 -0
  101. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/co_branding_requirement.py +0 -0
  102. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/creative_action.py +0 -0
  103. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/creative_agent_capability.py +0 -0
  104. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/creative_sort_field.py +0 -0
  105. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/creative_status.py +0 -0
  106. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/daast_tracking_event.py +0 -0
  107. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/daast_version.py +0 -0
  108. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/delivery_type.py +0 -0
  109. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/dimension_unit.py +0 -0
  110. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/feed_format.py +0 -0
  111. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/feedback_source.py +0 -0
  112. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/format_category.py +0 -0
  113. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/format_id_parameter.py +0 -0
  114. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/frequency_cap_scope.py +0 -0
  115. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/history_entry_type.py +0 -0
  116. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/http_method.py +0 -0
  117. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/identifier_types.py +0 -0
  118. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/javascript_module_type.py +0 -0
  119. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/landing_page_requirement.py +0 -0
  120. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/markdown_flavor.py +0 -0
  121. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/media_buy_status.py +0 -0
  122. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/metric_type.py +0 -0
  123. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/notification_type.py +0 -0
  124. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/pacing.py +0 -0
  125. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/preview_output_format.py +0 -0
  126. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/pricing_model.py +0 -0
  127. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/property_type.py +0 -0
  128. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/publisher_identifier_types.py +0 -0
  129. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/reporting_frequency.py +0 -0
  130. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/signal_catalog_type.py +0 -0
  131. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/sort_direction.py +0 -0
  132. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/standard_format_ids.py +0 -0
  133. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/task_status.py +0 -0
  134. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/task_type.py +0 -0
  135. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/update_frequency.py +0 -0
  136. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/url_asset_type.py +0 -0
  137. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/validation_mode.py +0 -0
  138. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/vast_tracking_event.py +0 -0
  139. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/vast_version.py +0 -0
  140. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/webhook_response_type.py +0 -0
  141. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/webhook_security_method.py +0 -0
  142. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/__init__.py +0 -0
  143. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/build_creative_request.py +0 -0
  144. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/build_creative_response.py +0 -0
  145. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/create_media_buy_async_response_input_required.py +0 -0
  146. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/create_media_buy_async_response_submitted.py +0 -0
  147. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/create_media_buy_async_response_working.py +0 -0
  148. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/create_media_buy_request.py +0 -0
  149. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/create_media_buy_response.py +0 -0
  150. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/get_media_buy_delivery_request.py +0 -0
  151. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/get_media_buy_delivery_response.py +0 -0
  152. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/get_products_async_response_input_required.py +0 -0
  153. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/get_products_async_response_submitted.py +0 -0
  154. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/get_products_async_response_working.py +0 -0
  155. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/get_products_request.py +0 -0
  156. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/get_products_response.py +0 -0
  157. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/list_authorized_properties_request.py +0 -0
  158. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/list_authorized_properties_response.py +0 -0
  159. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/list_creative_formats_request.py +0 -0
  160. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/list_creative_formats_response.py +0 -0
  161. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/list_creatives_request.py +0 -0
  162. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/list_creatives_response.py +0 -0
  163. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/package_request.py +0 -0
  164. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/provide_performance_feedback_request.py +0 -0
  165. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/provide_performance_feedback_response.py +0 -0
  166. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/sync_creatives_async_response_input_required.py +0 -0
  167. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/sync_creatives_async_response_submitted.py +0 -0
  168. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/sync_creatives_async_response_working.py +0 -0
  169. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/sync_creatives_request.py +0 -0
  170. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/sync_creatives_response.py +0 -0
  171. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/update_media_buy_async_response_input_required.py +0 -0
  172. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/update_media_buy_async_response_submitted.py +0 -0
  173. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/update_media_buy_async_response_working.py +0 -0
  174. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/update_media_buy_request.py +0 -0
  175. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/update_media_buy_response.py +0 -0
  176. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/__init__.py +0 -0
  177. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/cpc_option.py +0 -0
  178. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/cpcv_option.py +0 -0
  179. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/cpm_auction_option.py +0 -0
  180. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/cpm_fixed_option.py +0 -0
  181. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/cpp_option.py +0 -0
  182. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/cpv_option.py +0 -0
  183. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/flat_rate_option.py +0 -0
  184. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/vcpm_auction_option.py +0 -0
  185. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/vcpm_fixed_option.py +0 -0
  186. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/protocols/__init__.py +0 -0
  187. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/protocols/adcp_extension.py +0 -0
  188. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/signals/__init__.py +0 -0
  189. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/signals/activate_signal_request.py +0 -0
  190. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/signals/activate_signal_response.py +0 -0
  191. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/signals/get_signals_request.py +0 -0
  192. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/signals/get_signals_response.py +0 -0
  193. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/utils/__init__.py +0 -0
  194. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/utils/operation_id.py +0 -0
  195. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/utils/preview_cache.py +0 -0
  196. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/validation.py +0 -0
  197. {adcp-2.16.0 → adcp-2.17.0}/src/adcp/webhooks.py +0 -0
  198. {adcp-2.16.0 → adcp-2.17.0}/src/adcp.egg-info/SOURCES.txt +0 -0
  199. {adcp-2.16.0 → adcp-2.17.0}/src/adcp.egg-info/dependency_links.txt +0 -0
  200. {adcp-2.16.0 → adcp-2.17.0}/src/adcp.egg-info/entry_points.txt +0 -0
  201. {adcp-2.16.0 → adcp-2.17.0}/src/adcp.egg-info/requires.txt +0 -0
  202. {adcp-2.16.0 → adcp-2.17.0}/src/adcp.egg-info/top_level.txt +0 -0
  203. {adcp-2.16.0 → adcp-2.17.0}/tests/test_adagents.py +0 -0
  204. {adcp-2.16.0 → adcp-2.17.0}/tests/test_cli.py +0 -0
  205. {adcp-2.16.0 → adcp-2.17.0}/tests/test_client.py +0 -0
  206. {adcp-2.16.0 → adcp-2.17.0}/tests/test_code_generation.py +0 -0
  207. {adcp-2.16.0 → adcp-2.17.0}/tests/test_discriminated_unions.py +0 -0
  208. {adcp-2.16.0 → adcp-2.17.0}/tests/test_format_id_validation.py +0 -0
  209. {adcp-2.16.0 → adcp-2.17.0}/tests/test_helpers.py +0 -0
  210. {adcp-2.16.0 → adcp-2.17.0}/tests/test_preview_html.py +0 -0
  211. {adcp-2.16.0 → adcp-2.17.0}/tests/test_protocols.py +0 -0
  212. {adcp-2.16.0 → adcp-2.17.0}/tests/test_public_api.py +0 -0
  213. {adcp-2.16.0 → adcp-2.17.0}/tests/test_response_str.py +0 -0
  214. {adcp-2.16.0 → adcp-2.17.0}/tests/test_simple_api.py +0 -0
  215. {adcp-2.16.0 → adcp-2.17.0}/tests/test_type_aliases.py +0 -0
  216. {adcp-2.16.0 → adcp-2.17.0}/tests/test_type_coercion.py +0 -0
  217. {adcp-2.16.0 → adcp-2.17.0}/tests/test_webhook_handling.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adcp
3
- Version: 2.16.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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "adcp"
7
- version = "2.16.0"
7
+ version = "2.17.0"
8
8
  description = "Official Python client for the Ad Context Protocol (AdCP)"
9
9
  authors = [
10
10
  {name = "AdCP Community", email = "maintainers@adcontextprotocol.org"}
@@ -186,7 +186,7 @@ from adcp.webhooks import (
186
186
  get_adcp_signed_headers_for_webhook,
187
187
  )
188
188
 
189
- __version__ = "2.16.0"
189
+ __version__ = "2.17.0"
190
190
 
191
191
 
192
192
  def get_adcp_version() -> str:
@@ -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.16.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
@@ -0,0 +1,285 @@
1
+ from __future__ import annotations
2
+
3
+ """Tests for response parsing utilities."""
4
+
5
+ import json
6
+
7
+ import pytest
8
+ from pydantic import BaseModel, Field
9
+
10
+ from adcp.utils.response_parser import parse_json_or_text, parse_mcp_content
11
+
12
+
13
+ class SampleResponse(BaseModel):
14
+ """Sample response type for testing."""
15
+
16
+ message: str
17
+ count: int
18
+ items: list[str] = Field(default_factory=list)
19
+
20
+
21
+ class TestParseMCPContent:
22
+ """Tests for parse_mcp_content function."""
23
+
24
+ def test_parse_text_content_with_json(self):
25
+ """Test parsing MCP text content containing JSON."""
26
+ content = [
27
+ {
28
+ "type": "text",
29
+ "text": json.dumps({"message": "Hello", "count": 42, "items": ["a", "b"]}),
30
+ }
31
+ ]
32
+
33
+ result = parse_mcp_content(content, SampleResponse)
34
+
35
+ assert isinstance(result, SampleResponse)
36
+ assert result.message == "Hello"
37
+ assert result.count == 42
38
+ assert result.items == ["a", "b"]
39
+
40
+ def test_parse_multiple_content_items(self):
41
+ """Test parsing MCP content with multiple items, returns first valid."""
42
+ content = [
43
+ {"type": "text", "text": "Not JSON"},
44
+ {
45
+ "type": "text",
46
+ "text": json.dumps({"message": "Valid", "count": 10}),
47
+ },
48
+ ]
49
+
50
+ result = parse_mcp_content(content, SampleResponse)
51
+
52
+ assert result.message == "Valid"
53
+ assert result.count == 10
54
+
55
+ def test_empty_content_raises_error(self):
56
+ """Test that empty content array raises ValueError."""
57
+ with pytest.raises(ValueError, match="Empty MCP content array"):
58
+ parse_mcp_content([], SampleResponse)
59
+
60
+ def test_no_valid_content_raises_error(self):
61
+ """Test that content with no valid data raises ValueError."""
62
+ content = [
63
+ {"type": "text", "text": "Not JSON"},
64
+ {"type": "other", "data": "something"},
65
+ ]
66
+
67
+ with pytest.raises(ValueError, match="No valid SampleResponse data found"):
68
+ parse_mcp_content(content, SampleResponse)
69
+
70
+ def test_invalid_schema_raises_error(self):
71
+ """Test that content not matching schema raises ValueError."""
72
+ content = [
73
+ {
74
+ "type": "text",
75
+ "text": json.dumps({"wrong_field": "value"}),
76
+ }
77
+ ]
78
+
79
+ with pytest.raises(ValueError, match="doesn't match expected schema"):
80
+ parse_mcp_content(content, SampleResponse)
81
+
82
+ def test_empty_text_content_skipped(self):
83
+ """Test that empty text content is skipped."""
84
+ content = [
85
+ {"type": "text", "text": ""},
86
+ {
87
+ "type": "text",
88
+ "text": json.dumps({"message": "Found", "count": 5}),
89
+ },
90
+ ]
91
+
92
+ result = parse_mcp_content(content, SampleResponse)
93
+ assert result.message == "Found"
94
+
95
+
96
+ class TestParseJSONOrText:
97
+ """Tests for parse_json_or_text function."""
98
+
99
+ def test_parse_dict_directly(self):
100
+ """Test parsing dict data directly."""
101
+ data = {"message": "Hello", "count": 42}
102
+
103
+ result = parse_json_or_text(data, SampleResponse)
104
+
105
+ assert result.message == "Hello"
106
+ assert result.count == 42
107
+
108
+ def test_parse_json_string(self):
109
+ """Test parsing JSON string."""
110
+ data = json.dumps({"message": "World", "count": 100})
111
+
112
+ result = parse_json_or_text(data, SampleResponse)
113
+
114
+ assert result.message == "World"
115
+ assert result.count == 100
116
+
117
+ def test_invalid_json_string_raises_error(self):
118
+ """Test that invalid JSON string raises ValueError."""
119
+ with pytest.raises(ValueError, match="not valid JSON"):
120
+ parse_json_or_text("Not JSON at all", SampleResponse)
121
+
122
+ def test_dict_not_matching_schema_raises_error(self):
123
+ """Test that dict not matching schema raises ValueError."""
124
+ with pytest.raises(ValueError, match="doesn't match expected schema"):
125
+ parse_json_or_text({"wrong": "data"}, SampleResponse)
126
+
127
+ def test_unsupported_type_raises_error(self):
128
+ """Test that unsupported data type raises ValueError."""
129
+ with pytest.raises(ValueError, match="Cannot parse response of type"):
130
+ parse_json_or_text(12345, SampleResponse) # type: ignore[arg-type]
131
+
132
+ def test_json_string_not_matching_schema_raises_error(self):
133
+ """Test that JSON string not matching schema raises ValueError."""
134
+ data = json.dumps({"invalid": "structure"})
135
+
136
+ with pytest.raises(ValueError, match="doesn't match expected schema"):
137
+ parse_json_or_text(data, SampleResponse)
138
+
139
+
140
+ class ProductResponse(BaseModel):
141
+ """Response type without protocol fields for testing protocol field stripping."""
142
+
143
+ products: list[str]
144
+ total: int = 0
145
+
146
+
147
+ class TestProtocolFieldExtraction:
148
+ """Tests for protocol field extraction from A2A responses.
149
+
150
+ A2A servers may include protocol-level fields (message, context_id, data)
151
+ that are not part of task-specific response schemas. These are separated
152
+ for task data validation, but preserved at the TaskResult level.
153
+
154
+ See: https://github.com/adcontextprotocol/adcp-client-python/issues/109
155
+ """
156
+
157
+ def test_response_with_message_field_separated(self):
158
+ """Test that protocol 'message' field is separated before validation."""
159
+ # A2A server returns task data with protocol message mixed in
160
+ data = {
161
+ "message": "No products matched your requirements.",
162
+ "products": ["product-1", "product-2"],
163
+ "total": 2,
164
+ }
165
+
166
+ result = parse_json_or_text(data, ProductResponse)
167
+
168
+ assert isinstance(result, ProductResponse)
169
+ assert result.products == ["product-1", "product-2"]
170
+ assert result.total == 2
171
+
172
+ def test_response_with_context_id_separated(self):
173
+ """Test that protocol 'context_id' field is separated before validation."""
174
+ data = {
175
+ "context_id": "session-123",
176
+ "products": ["product-1"],
177
+ "total": 1,
178
+ }
179
+
180
+ result = parse_json_or_text(data, ProductResponse)
181
+
182
+ assert isinstance(result, ProductResponse)
183
+ assert result.products == ["product-1"]
184
+
185
+ def test_response_with_multiple_protocol_fields_separated(self):
186
+ """Test that multiple protocol fields are separated."""
187
+ data = {
188
+ "message": "Found products",
189
+ "context_id": "session-456",
190
+ "products": ["a", "b", "c"],
191
+ "total": 3,
192
+ }
193
+
194
+ result = parse_json_or_text(data, ProductResponse)
195
+
196
+ assert isinstance(result, ProductResponse)
197
+ assert result.products == ["a", "b", "c"]
198
+ assert result.total == 3
199
+
200
+ def test_response_with_data_wrapper_extracted(self):
201
+ """Test that ProtocolResponse 'data' wrapper is extracted."""
202
+ # Full ProtocolResponse format: {"message": "...", "data": {...task_data...}}
203
+ data = {
204
+ "message": "Operation completed",
205
+ "context_id": "ctx-789",
206
+ "data": {
207
+ "products": ["wrapped-product"],
208
+ "total": 1,
209
+ },
210
+ }
211
+
212
+ result = parse_json_or_text(data, ProductResponse)
213
+
214
+ assert isinstance(result, ProductResponse)
215
+ assert result.products == ["wrapped-product"]
216
+ assert result.total == 1
217
+
218
+ def test_response_with_payload_wrapper_extracted(self):
219
+ """Test that ProtocolEnvelope 'payload' wrapper is extracted."""
220
+ # Full ProtocolEnvelope format
221
+ data = {
222
+ "message": "Operation completed",
223
+ "status": "completed",
224
+ "task_id": "task-123",
225
+ "timestamp": "2025-01-01T00:00:00Z",
226
+ "payload": {
227
+ "products": ["envelope-product"],
228
+ "total": 1,
229
+ },
230
+ }
231
+
232
+ result = parse_json_or_text(data, ProductResponse)
233
+
234
+ assert isinstance(result, ProductResponse)
235
+ assert result.products == ["envelope-product"]
236
+ assert result.total == 1
237
+
238
+ def test_exact_match_still_works(self):
239
+ """Test that responses exactly matching schema still work."""
240
+ data = {
241
+ "products": ["exact-match"],
242
+ "total": 1,
243
+ }
244
+
245
+ result = parse_json_or_text(data, ProductResponse)
246
+
247
+ assert result.products == ["exact-match"]
248
+ assert result.total == 1
249
+
250
+ def test_json_string_with_protocol_fields(self):
251
+ """Test JSON string with protocol fields is parsed correctly."""
252
+ data = json.dumps(
253
+ {
254
+ "message": "Success",
255
+ "products": ["from-json-string"],
256
+ "total": 1,
257
+ }
258
+ )
259
+
260
+ result = parse_json_or_text(data, ProductResponse)
261
+
262
+ assert result.products == ["from-json-string"]
263
+
264
+ def test_invalid_data_after_separation_raises_error(self):
265
+ """Test that invalid data still raises error after separation."""
266
+ data = {
267
+ "message": "Some message",
268
+ "wrong_field": "value",
269
+ }
270
+
271
+ with pytest.raises(ValueError, match="doesn't match expected schema"):
272
+ parse_json_or_text(data, ProductResponse)
273
+
274
+ def test_model_with_message_field_validates_directly(self):
275
+ """Test that models containing 'message' field validate without separation."""
276
+ # SampleResponse has a 'message' field, so it should validate directly
277
+ data = {
278
+ "message": "Hello",
279
+ "count": 42,
280
+ }
281
+
282
+ result = parse_json_or_text(data, SampleResponse)
283
+
284
+ assert result.message == "Hello"
285
+ assert result.count == 42
@@ -1,137 +0,0 @@
1
- from __future__ import annotations
2
-
3
- """Tests for response parsing utilities."""
4
-
5
- import json
6
-
7
- import pytest
8
- from pydantic import BaseModel, Field
9
-
10
- from adcp.utils.response_parser import parse_json_or_text, parse_mcp_content
11
-
12
-
13
- class SampleResponse(BaseModel):
14
- """Sample response type for testing."""
15
-
16
- message: str
17
- count: int
18
- items: list[str] = Field(default_factory=list)
19
-
20
-
21
- class TestParseMCPContent:
22
- """Tests for parse_mcp_content function."""
23
-
24
- def test_parse_text_content_with_json(self):
25
- """Test parsing MCP text content containing JSON."""
26
- content = [
27
- {
28
- "type": "text",
29
- "text": json.dumps({"message": "Hello", "count": 42, "items": ["a", "b"]}),
30
- }
31
- ]
32
-
33
- result = parse_mcp_content(content, SampleResponse)
34
-
35
- assert isinstance(result, SampleResponse)
36
- assert result.message == "Hello"
37
- assert result.count == 42
38
- assert result.items == ["a", "b"]
39
-
40
- def test_parse_multiple_content_items(self):
41
- """Test parsing MCP content with multiple items, returns first valid."""
42
- content = [
43
- {"type": "text", "text": "Not JSON"},
44
- {
45
- "type": "text",
46
- "text": json.dumps({"message": "Valid", "count": 10}),
47
- },
48
- ]
49
-
50
- result = parse_mcp_content(content, SampleResponse)
51
-
52
- assert result.message == "Valid"
53
- assert result.count == 10
54
-
55
- def test_empty_content_raises_error(self):
56
- """Test that empty content array raises ValueError."""
57
- with pytest.raises(ValueError, match="Empty MCP content array"):
58
- parse_mcp_content([], SampleResponse)
59
-
60
- def test_no_valid_content_raises_error(self):
61
- """Test that content with no valid data raises ValueError."""
62
- content = [
63
- {"type": "text", "text": "Not JSON"},
64
- {"type": "other", "data": "something"},
65
- ]
66
-
67
- with pytest.raises(ValueError, match="No valid SampleResponse data found"):
68
- parse_mcp_content(content, SampleResponse)
69
-
70
- def test_invalid_schema_raises_error(self):
71
- """Test that content not matching schema raises ValueError."""
72
- content = [
73
- {
74
- "type": "text",
75
- "text": json.dumps({"wrong_field": "value"}),
76
- }
77
- ]
78
-
79
- with pytest.raises(ValueError, match="doesn't match expected schema"):
80
- parse_mcp_content(content, SampleResponse)
81
-
82
- def test_empty_text_content_skipped(self):
83
- """Test that empty text content is skipped."""
84
- content = [
85
- {"type": "text", "text": ""},
86
- {
87
- "type": "text",
88
- "text": json.dumps({"message": "Found", "count": 5}),
89
- },
90
- ]
91
-
92
- result = parse_mcp_content(content, SampleResponse)
93
- assert result.message == "Found"
94
-
95
-
96
- class TestParseJSONOrText:
97
- """Tests for parse_json_or_text function."""
98
-
99
- def test_parse_dict_directly(self):
100
- """Test parsing dict data directly."""
101
- data = {"message": "Hello", "count": 42}
102
-
103
- result = parse_json_or_text(data, SampleResponse)
104
-
105
- assert result.message == "Hello"
106
- assert result.count == 42
107
-
108
- def test_parse_json_string(self):
109
- """Test parsing JSON string."""
110
- data = json.dumps({"message": "World", "count": 100})
111
-
112
- result = parse_json_or_text(data, SampleResponse)
113
-
114
- assert result.message == "World"
115
- assert result.count == 100
116
-
117
- def test_invalid_json_string_raises_error(self):
118
- """Test that invalid JSON string raises ValueError."""
119
- with pytest.raises(ValueError, match="not valid JSON"):
120
- parse_json_or_text("Not JSON at all", SampleResponse)
121
-
122
- def test_dict_not_matching_schema_raises_error(self):
123
- """Test that dict not matching schema raises ValueError."""
124
- with pytest.raises(ValueError, match="doesn't match expected schema"):
125
- parse_json_or_text({"wrong": "data"}, SampleResponse)
126
-
127
- def test_unsupported_type_raises_error(self):
128
- """Test that unsupported data type raises ValueError."""
129
- with pytest.raises(ValueError, match="Cannot parse response of type"):
130
- parse_json_or_text(12345, SampleResponse) # type: ignore[arg-type]
131
-
132
- def test_json_string_not_matching_schema_raises_error(self):
133
- """Test that JSON string not matching schema raises ValueError."""
134
- data = json.dumps({"invalid": "structure"})
135
-
136
- with pytest.raises(ValueError, match="doesn't match expected schema"):
137
- parse_json_or_text(data, SampleResponse)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes