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.
- {adcp-2.16.0/src/adcp.egg-info → adcp-2.17.0}/PKG-INFO +1 -1
- {adcp-2.16.0 → adcp-2.17.0}/pyproject.toml +1 -1
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/__init__.py +1 -1
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/utils/response_parser.py +81 -6
- {adcp-2.16.0 → adcp-2.17.0/src/adcp.egg-info}/PKG-INFO +1 -1
- adcp-2.17.0/tests/test_response_parser.py +285 -0
- adcp-2.16.0/tests/test_response_parser.py +0 -137
- {adcp-2.16.0 → adcp-2.17.0}/LICENSE +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/MANIFEST.in +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/README.md +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/setup.cfg +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/ADCP_VERSION +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/__main__.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/adagents.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/client.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/config.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/exceptions.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/protocols/__init__.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/protocols/a2a.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/protocols/base.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/protocols/mcp.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/py.typed +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/simple.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/testing/__init__.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/testing/test_helpers.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/__init__.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/_ergonomic.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/_generated.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/aliases.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/base.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/coercion.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/core.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/__init__.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/adagents.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/__init__.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/activation_key.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/__init__.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/audio_asset.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/css_asset.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/daast_asset.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/html_asset.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/image_asset.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/javascript_asset.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/text_asset.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/url_asset.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/vast_asset.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/video_asset.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/assets/webhook_asset.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/async_response_data.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/brand_manifest.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/brand_manifest_ref.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/context.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/creative_asset.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/creative_assignment.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/creative_filters.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/creative_manifest.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/creative_policy.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/delivery_metrics.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/deployment.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/destination.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/error.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/ext.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/format.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/format_id.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/frequency_cap.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/mcp_webhook_payload.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/measurement.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/media_buy.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/package.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/performance_feedback.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/placement.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/pricing_option.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/product.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/product_filters.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/promoted_offerings.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/promoted_products.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/property.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/property_id.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/property_tag.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/protocol_envelope.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/publisher_property_selector.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/push_notification_config.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/reporting_capabilities.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/response.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/signal_filters.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/start_timing.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/sub_asset.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/core/targeting.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/creative/__init__.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/creative/list_creative_formats_request.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/creative/list_creative_formats_response.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/creative/preview_creative_request.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/creative/preview_creative_response.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/creative/preview_render.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/__init__.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/adcp_domain.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/asset_content_type.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/auth_scheme.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/available_metric.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/channels.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/co_branding_requirement.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/creative_action.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/creative_agent_capability.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/creative_sort_field.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/creative_status.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/daast_tracking_event.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/daast_version.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/delivery_type.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/dimension_unit.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/feed_format.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/feedback_source.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/format_category.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/format_id_parameter.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/frequency_cap_scope.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/history_entry_type.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/http_method.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/identifier_types.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/javascript_module_type.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/landing_page_requirement.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/markdown_flavor.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/media_buy_status.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/metric_type.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/notification_type.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/pacing.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/preview_output_format.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/pricing_model.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/property_type.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/publisher_identifier_types.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/reporting_frequency.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/signal_catalog_type.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/sort_direction.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/standard_format_ids.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/task_status.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/task_type.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/update_frequency.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/url_asset_type.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/validation_mode.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/vast_tracking_event.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/vast_version.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/webhook_response_type.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/enums/webhook_security_method.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/__init__.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/build_creative_request.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/build_creative_response.py +0 -0
- {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
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/create_media_buy_async_response_submitted.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/create_media_buy_async_response_working.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/create_media_buy_request.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/create_media_buy_response.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/get_media_buy_delivery_request.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/get_media_buy_delivery_response.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/get_products_async_response_input_required.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/get_products_async_response_submitted.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/get_products_async_response_working.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/get_products_request.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/get_products_response.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/list_authorized_properties_request.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/list_authorized_properties_response.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/list_creative_formats_request.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/list_creative_formats_response.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/list_creatives_request.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/list_creatives_response.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/package_request.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/provide_performance_feedback_request.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/provide_performance_feedback_response.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/sync_creatives_async_response_input_required.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/sync_creatives_async_response_submitted.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/sync_creatives_async_response_working.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/sync_creatives_request.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/sync_creatives_response.py +0 -0
- {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
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/update_media_buy_async_response_submitted.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/update_media_buy_async_response_working.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/update_media_buy_request.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/media_buy/update_media_buy_response.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/__init__.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/cpc_option.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/cpcv_option.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/cpm_auction_option.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/cpm_fixed_option.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/cpp_option.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/cpv_option.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/flat_rate_option.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/vcpm_auction_option.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/pricing_options/vcpm_fixed_option.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/protocols/__init__.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/protocols/adcp_extension.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/signals/__init__.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/signals/activate_signal_request.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/signals/activate_signal_response.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/signals/get_signals_request.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/types/generated_poc/signals/get_signals_response.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/utils/__init__.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/utils/operation_id.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/utils/preview_cache.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/validation.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp/webhooks.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp.egg-info/SOURCES.txt +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp.egg-info/dependency_links.txt +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp.egg-info/entry_points.txt +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp.egg-info/requires.txt +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/src/adcp.egg-info/top_level.txt +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/tests/test_adagents.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/tests/test_cli.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/tests/test_client.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/tests/test_code_generation.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/tests/test_discriminated_unions.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/tests/test_format_id_validation.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/tests/test_helpers.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/tests/test_preview_html.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/tests/test_protocols.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/tests/test_public_api.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/tests/test_response_str.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/tests/test_simple_api.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/tests/test_type_aliases.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/tests/test_type_coercion.py +0 -0
- {adcp-2.16.0 → adcp-2.17.0}/tests/test_webhook_handling.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "adcp"
|
|
7
|
-
version = "2.
|
|
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"}
|
|
@@ -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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
|
@@ -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
|
|
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
|