From 99e59792b2c676040cef5c8a251aee68bc306848 Mon Sep 17 00:00:00 2001 From: Segi Date: Tue, 25 Aug 2026 11:06:56 -0700 Subject: [PATCH] feat(models): update Python SDK schemas for UCP release 2026-08-25 - Update schema preprocessing pipeline for v2026-08-25 response definitions - Regenerate Pydantic models from UCP release/2026-08-25 JSON schemas - Restructure models into common, shopping, and transport verticals - Add Location Search/Lookup, Loyalty, Request Constraints, Permalinks, and Actions models - Move shared primitives and payment extensions to common namespace - Bump package version to 0.5.0 in pyproject.toml and update README.md --- .codespellignore | 1 + .github/workflows/conventional-commits.yml | 3 +- .github/workflows/linter.yml | 11 +- .github/workflows/tests.yml | 10 +- .gitignore | 2 + README.md | 23 +- generate_models.sh | 20 +- postprocess_models.py | 2 +- preprocess_schemas.py | 24 +- pyproject.toml | 17 +- src/ucp_sdk/__init__.py | 17 + src/ucp_sdk/models/__init__.py | 15 + src/ucp_sdk/models/schemas/capability.py | 74 +---- .../models/schemas/common/identity_linking.py | 18 +- .../models/schemas/common/location_lookup.py | 101 ++++++ .../models/schemas/common/location_search.py | 85 +++++ src/ucp_sdk/models/schemas/common/loyalty.py | 297 ++++++++++++++++++ .../payment_ap2_mandate.py} | 8 +- .../schemas/common/payment_authentication.py | 101 ++++++ .../schemas/common/payment_split_payments.py | 80 +++++ .../models/schemas/common/payment_terms.py | 99 ++++++ .../models/schemas/common/types/__init__.py | 17 + .../models/schemas/common/types/actions.py | 56 ++++ .../schemas/common/types/amenity_type.py | 46 +++ .../{shopping => common}/types/amount.py | 3 +- .../types/available_payment_instrument.py | 8 +- .../{shopping => common}/types/binding.py | 14 +- .../types/business_split_payments_config.py | 48 +++ .../types/card_credential.py | 4 +- .../types/card_payment_instrument.py | 45 +-- .../common/types/constraint_expression.py | 101 ++++++ .../{shopping => common}/types/context.py | 33 +- .../types/context_create_request.py | 33 +- .../types/context_update_request.py | 33 +- .../models/schemas/common/types/daily_hour.py | 49 +++ .../common/types/daily_hour_create_request.py | 33 ++ .../common/types/daily_hour_update_request.py | 33 ++ .../{shopping => common}/types/description.py | 0 .../{shopping => common}/types/error_code.py | 2 +- .../types/error_code_create_request.py | 2 +- .../types/error_code_update_request.py | 2 +- .../types/error_response.py | 0 .../schemas/common/types/exception_hour.py | 47 +++ .../types/exception_hour_create_request.py | 33 ++ .../types/exception_hour_update_request.py | 33 ++ .../models/schemas/common/types/geo.py | 39 +++ .../common/types/geo_create_request.py | 39 +++ .../common/types/geo_update_request.py | 39 +++ .../{shopping => common}/types/info_code.py | 2 +- .../types/info_code_create_request.py | 2 +- .../types/info_code_update_request.py | 2 +- .../schemas/common/types/instrument_group.py | 43 +++ .../{shopping => common}/types/link.py | 0 .../models/schemas/common/types/locality.py | 43 +++ .../common/types/locality_create_request.py | 43 +++ .../common/types/locality_update_request.py | 43 +++ .../models/schemas/common/types/location.py | 69 ++++ .../common/types/location_create_request.py | 47 +++ .../schemas/common/types/location_distance.py | 41 +++ .../schemas/common/types/location_filter.py | 90 ++++++ .../schemas/common/types/location_serves.py | 86 +++++ .../types/location_serves_create_request.py | 86 +++++ .../types/location_serves_update_request.py | 86 +++++ .../types/location_summary.py} | 8 +- .../types/location_summary_create_request.py} | 8 +- .../types/location_summary_update_request.py} | 16 +- .../common/types/location_update_request.py | 47 +++ .../models/schemas/common/types/measure.py | 37 +++ .../common/types/measure_create_request.py | 37 +++ .../common/types/measure_update_request.py | 37 +++ .../{shopping => common}/types/media.py | 2 +- .../{shopping => common}/types/message.py | 0 .../types/message_create_request.py | 0 .../types/message_error.py | 4 +- .../types/message_error_create_request.py | 4 +- .../types/message_error_update_request.py | 4 +- .../types/message_info.py | 2 +- .../types/message_info_create_request.py | 2 +- .../types/message_info_update_request.py | 2 +- .../types/message_update_request.py | 0 .../types/message_warning.py | 2 +- .../types/message_warning_create_request.py | 2 +- .../types/message_warning_update_request.py | 2 +- .../common/types/network_token_credential.py | 67 ++++ .../{shopping => common}/types/pagination.py | 4 +- .../schemas/common/types/pan_credential.py | 59 ++++ .../{shopping => common/types}/payment.py | 2 +- .../types}/payment_complete_request.py | 2 +- .../types}/payment_create_request.py | 2 +- .../types/payment_credential.py | 0 .../payment_credential_complete_request.py | 0 .../payment_credential_create_request.py | 0 .../payment_credential_update_request.py | 0 .../types/payment_identity.py | 2 +- .../types/payment_instrument.py | 2 +- .../payment_instrument_complete_request.py | 2 +- .../payment_instrument_create_request.py | 2 +- .../payment_instrument_update_request.py | 2 +- .../schemas/common/types/payment_schedule.py | 54 ++++ .../schemas/common/types/payment_term.py | 50 +++ .../types}/payment_update_request.py | 2 +- .../models/schemas/common/types/policy.py | 50 +++ .../types/postal_address.py | 0 .../types/postal_address_complete_request.py | 0 .../types/postal_address_create_request.py | 0 .../types/postal_address_update_request.py | 0 .../{shopping => common}/types/price.py | 0 .../types/price_filter.py | 0 .../{shopping => common}/types/price_range.py | 2 +- .../schemas/common/types/quantity_unit.py | 37 +++ .../types/quantity_unit_create_request.py | 37 +++ .../types/quantity_unit_update_request.py | 37 +++ .../common/types/request_constraints.py | 72 +++++ .../types/reverse_domain_name.py | 13 +- .../reverse_domain_name_create_request.py | 13 +- .../reverse_domain_name_update_request.py | 13 +- .../{shopping => common}/types/signals.py | 2 +- .../types/signals_complete_request.py | 2 +- .../types/signals_create_request.py | 2 +- .../types/signals_update_request.py | 2 +- .../types/signed_amount.py | 11 +- .../types/signed_amount_create_request.py | 10 +- .../types/signed_amount_update_request.py | 10 +- .../schemas/common/types/time_interval.py | 39 +++ .../types/time_interval_create_request.py} | 16 +- .../types/time_interval_update_request.py | 31 ++ .../types/token_credential.py | 0 .../{shopping => common}/types/total.py | 0 .../types/total_create_request.py | 0 .../types/total_update_request.py | 0 .../{shopping => common}/types/totals.py | 0 .../types/totals_create_request.py | 0 .../types/totals_update_request.py | 0 .../models/schemas/common/types/unit.py | 43 +++ .../common/types/unit_create_request.py | 43 +++ .../common/types/unit_update_request.py | 43 +++ .../types/warning_code.py | 3 +- .../types/warning_code_create_request.py | 3 +- .../types/warning_code_update_request.py | 3 +- src/ucp_sdk/models/schemas/payment_handler.py | 2 +- src/ucp_sdk/models/schemas/profile.py | 127 ++++++++ src/ucp_sdk/models/schemas/service.py | 26 +- .../models/schemas/shopping/buyer_consent.py | 86 ++++- src/ucp_sdk/models/schemas/shopping/cart.py | 18 +- .../schemas/shopping/cart_create_request.py | 3 +- .../schemas/shopping/cart_update_request.py | 7 +- .../models/schemas/shopping/catalog_lookup.py | 63 ++-- .../models/schemas/shopping/catalog_search.py | 18 +- .../models/schemas/shopping/checkout.py | 23 +- .../shopping/checkout_complete_request.py | 4 +- .../shopping/checkout_create_request.py | 8 +- .../shopping/checkout_update_request.py | 8 +- .../models/schemas/shopping/discount.py | 6 +- .../models/schemas/shopping/fulfillment.py | 202 +++++++++++- src/ucp_sdk/models/schemas/shopping/order.py | 9 +- .../schemas/shopping/order_create_request.py | 3 +- .../schemas/shopping/order_update_request.py | 3 +- .../models/schemas/shopping/permalink.py | 52 +++ .../schemas/shopping/types/adjustment.py | 13 +- .../types/adjustment_create_request.py | 12 +- .../types/adjustment_update_request.py | 12 +- .../schemas/shopping/types/availability.py | 39 +++ .../types/business_fulfillment_config.py | 28 +- .../schemas/shopping/types/expectation.py | 12 +- .../types/expectation_create_request.py | 12 +- .../types/expectation_update_request.py | 12 +- .../types/fulfillment_available_method.py | 6 +- .../shopping/types/fulfillment_destination.py | 31 +- .../fulfillment_destination_create_request.py | 34 +- .../types/fulfillment_destination_filter.py | 37 +++ .../fulfillment_destination_update_request.py | 34 +- .../shopping/types/fulfillment_event.py | 4 +- .../types/fulfillment_event_create_request.py | 4 +- .../types/fulfillment_event_update_request.py | 4 +- .../shopping/types/fulfillment_method.py | 12 +- .../fulfillment_method_create_request.py | 28 +- .../fulfillment_method_update_request.py | 24 +- .../shopping/types/fulfillment_option.py | 21 +- ...t_config.py => fulfillment_option_base.py} | 38 +-- .../fulfillment_option_base_create_request.py | 31 ++ .../fulfillment_option_base_update_request.py | 31 ++ .../fulfillment_option_create_request.py | 10 +- .../fulfillment_option_update_request.py | 10 +- .../models/schemas/shopping/types/item.py | 14 +- .../shopping/types/item_create_request.py | 8 + .../shopping/types/item_update_request.py | 8 + .../schemas/shopping/types/line_item.py | 6 +- .../types/line_item_create_request.py | 4 +- .../types/line_item_update_request.py | 4 +- .../shopping/types/location_destination.py | 39 +++ .../location_destination_create_request.py | 35 +++ .../location_destination_update_request.py | 35 +++ .../schemas/shopping/types/order_line_item.py | 20 +- .../types/order_line_item_create_request.py | 21 +- .../types/order_line_item_update_request.py | 21 +- .../models/schemas/shopping/types/product.py | 9 +- .../schemas/shopping/types/search_filters.py | 2 +- .../shopping/types/shipping_destination.py | 8 +- .../shipping_destination_create_request.py | 10 +- .../shipping_destination_update_request.py | 10 +- .../schemas/shopping/types/unit_price.py | 74 +++++ .../models/schemas/shopping/types/variant.py | 108 +------ .../models/schemas/transports/a2a_message.py | 133 ++++++++ .../schemas/transports/embedded_message.py | 109 +++++++ .../models/schemas/transports/jsonrpc.py | 121 +++++++ .../schemas/transports/mcp_tool_call.py | 157 +++++++++ src/ucp_sdk/models/schemas/ucp.py | 56 +++- .../models/schemas/ucp_create_request.py | 56 +++- .../models/schemas/ucp_update_request.py | 56 +++- 209 files changed, 5043 insertions(+), 736 deletions(-) create mode 100644 src/ucp_sdk/__init__.py create mode 100644 src/ucp_sdk/models/__init__.py create mode 100644 src/ucp_sdk/models/schemas/common/location_lookup.py create mode 100644 src/ucp_sdk/models/schemas/common/location_search.py create mode 100644 src/ucp_sdk/models/schemas/common/loyalty.py rename src/ucp_sdk/models/schemas/{shopping/ap2_mandate.py => common/payment_ap2_mandate.py} (94%) create mode 100644 src/ucp_sdk/models/schemas/common/payment_authentication.py create mode 100644 src/ucp_sdk/models/schemas/common/payment_split_payments.py create mode 100644 src/ucp_sdk/models/schemas/common/payment_terms.py create mode 100644 src/ucp_sdk/models/schemas/common/types/__init__.py create mode 100644 src/ucp_sdk/models/schemas/common/types/actions.py create mode 100644 src/ucp_sdk/models/schemas/common/types/amenity_type.py rename src/ucp_sdk/models/schemas/{shopping => common}/types/amount.py (91%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/available_payment_instrument.py (70%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/binding.py (60%) create mode 100644 src/ucp_sdk/models/schemas/common/types/business_split_payments_config.py rename src/ucp_sdk/models/schemas/{shopping => common}/types/card_credential.py (73%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/card_payment_instrument.py (66%) create mode 100644 src/ucp_sdk/models/schemas/common/types/constraint_expression.py rename src/ucp_sdk/models/schemas/{shopping => common}/types/context.py (72%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/context_create_request.py (68%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/context_update_request.py (68%) create mode 100644 src/ucp_sdk/models/schemas/common/types/daily_hour.py create mode 100644 src/ucp_sdk/models/schemas/common/types/daily_hour_create_request.py create mode 100644 src/ucp_sdk/models/schemas/common/types/daily_hour_update_request.py rename src/ucp_sdk/models/schemas/{shopping => common}/types/description.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/error_code.py (92%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/error_code_create_request.py (92%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/error_code_update_request.py (92%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/error_response.py (100%) create mode 100644 src/ucp_sdk/models/schemas/common/types/exception_hour.py create mode 100644 src/ucp_sdk/models/schemas/common/types/exception_hour_create_request.py create mode 100644 src/ucp_sdk/models/schemas/common/types/exception_hour_update_request.py create mode 100644 src/ucp_sdk/models/schemas/common/types/geo.py create mode 100644 src/ucp_sdk/models/schemas/common/types/geo_create_request.py create mode 100644 src/ucp_sdk/models/schemas/common/types/geo_update_request.py rename src/ucp_sdk/models/schemas/{shopping => common}/types/info_code.py (90%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/info_code_create_request.py (90%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/info_code_update_request.py (90%) create mode 100644 src/ucp_sdk/models/schemas/common/types/instrument_group.py rename src/ucp_sdk/models/schemas/{shopping => common}/types/link.py (100%) create mode 100644 src/ucp_sdk/models/schemas/common/types/locality.py create mode 100644 src/ucp_sdk/models/schemas/common/types/locality_create_request.py create mode 100644 src/ucp_sdk/models/schemas/common/types/locality_update_request.py create mode 100644 src/ucp_sdk/models/schemas/common/types/location.py create mode 100644 src/ucp_sdk/models/schemas/common/types/location_create_request.py create mode 100644 src/ucp_sdk/models/schemas/common/types/location_distance.py create mode 100644 src/ucp_sdk/models/schemas/common/types/location_filter.py create mode 100644 src/ucp_sdk/models/schemas/common/types/location_serves.py create mode 100644 src/ucp_sdk/models/schemas/common/types/location_serves_create_request.py create mode 100644 src/ucp_sdk/models/schemas/common/types/location_serves_update_request.py rename src/ucp_sdk/models/schemas/{shopping/types/retail_location.py => common/types/location_summary.py} (84%) rename src/ucp_sdk/models/schemas/{shopping/types/account_info.py => common/types/location_summary_create_request.py} (74%) rename src/ucp_sdk/models/schemas/{shopping/types/retail_location_create_request.py => common/types/location_summary_update_request.py} (70%) create mode 100644 src/ucp_sdk/models/schemas/common/types/location_update_request.py create mode 100644 src/ucp_sdk/models/schemas/common/types/measure.py create mode 100644 src/ucp_sdk/models/schemas/common/types/measure_create_request.py create mode 100644 src/ucp_sdk/models/schemas/common/types/measure_update_request.py rename src/ucp_sdk/models/schemas/{shopping => common}/types/media.py (96%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/message.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/message_create_request.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/message_error.py (71%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/message_error_create_request.py (72%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/message_error_update_request.py (72%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/message_info.py (93%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/message_info_create_request.py (93%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/message_info_update_request.py (93%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/message_update_request.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/message_warning.py (95%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/message_warning_create_request.py (95%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/message_warning_update_request.py (95%) create mode 100644 src/ucp_sdk/models/schemas/common/types/network_token_credential.py rename src/ucp_sdk/models/schemas/{shopping => common}/types/pagination.py (84%) create mode 100644 src/ucp_sdk/models/schemas/common/types/pan_credential.py rename src/ucp_sdk/models/schemas/{shopping => common/types}/payment.py (96%) rename src/ucp_sdk/models/schemas/{shopping => common/types}/payment_complete_request.py (95%) rename src/ucp_sdk/models/schemas/{shopping => common/types}/payment_create_request.py (95%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/payment_credential.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/payment_credential_complete_request.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/payment_credential_create_request.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/payment_credential_update_request.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/payment_identity.py (93%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/payment_instrument.py (84%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/payment_instrument_complete_request.py (85%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/payment_instrument_create_request.py (85%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/payment_instrument_update_request.py (85%) create mode 100644 src/ucp_sdk/models/schemas/common/types/payment_schedule.py create mode 100644 src/ucp_sdk/models/schemas/common/types/payment_term.py rename src/ucp_sdk/models/schemas/{shopping => common/types}/payment_update_request.py (95%) create mode 100644 src/ucp_sdk/models/schemas/common/types/policy.py rename src/ucp_sdk/models/schemas/{shopping => common}/types/postal_address.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/postal_address_complete_request.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/postal_address_create_request.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/postal_address_update_request.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/price.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/price_filter.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/price_range.py (92%) create mode 100644 src/ucp_sdk/models/schemas/common/types/quantity_unit.py create mode 100644 src/ucp_sdk/models/schemas/common/types/quantity_unit_create_request.py create mode 100644 src/ucp_sdk/models/schemas/common/types/quantity_unit_update_request.py create mode 100644 src/ucp_sdk/models/schemas/common/types/request_constraints.py rename src/ucp_sdk/models/schemas/{shopping => common}/types/reverse_domain_name.py (57%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/reverse_domain_name_create_request.py (58%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/reverse_domain_name_update_request.py (58%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/signals.py (95%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/signals_complete_request.py (95%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/signals_create_request.py (95%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/signals_update_request.py (95%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/signed_amount.py (84%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/signed_amount_create_request.py (85%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/signed_amount_update_request.py (85%) create mode 100644 src/ucp_sdk/models/schemas/common/types/time_interval.py rename src/ucp_sdk/models/schemas/{shopping/types/retail_location_update_request.py => common/types/time_interval_create_request.py} (68%) create mode 100644 src/ucp_sdk/models/schemas/common/types/time_interval_update_request.py rename src/ucp_sdk/models/schemas/{shopping => common}/types/token_credential.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/total.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/total_create_request.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/total_update_request.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/totals.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/totals_create_request.py (100%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/totals_update_request.py (100%) create mode 100644 src/ucp_sdk/models/schemas/common/types/unit.py create mode 100644 src/ucp_sdk/models/schemas/common/types/unit_create_request.py create mode 100644 src/ucp_sdk/models/schemas/common/types/unit_update_request.py rename src/ucp_sdk/models/schemas/{shopping => common}/types/warning_code.py (88%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/warning_code_create_request.py (88%) rename src/ucp_sdk/models/schemas/{shopping => common}/types/warning_code_update_request.py (88%) create mode 100644 src/ucp_sdk/models/schemas/profile.py create mode 100644 src/ucp_sdk/models/schemas/shopping/permalink.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/availability.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_filter.py rename src/ucp_sdk/models/schemas/shopping/types/{merchant_fulfillment_config.py => fulfillment_option_base.py} (54%) create mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_base_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_base_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/location_destination.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/location_destination_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/location_destination_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/unit_price.py create mode 100644 src/ucp_sdk/models/schemas/transports/a2a_message.py create mode 100644 src/ucp_sdk/models/schemas/transports/embedded_message.py create mode 100644 src/ucp_sdk/models/schemas/transports/jsonrpc.py create mode 100644 src/ucp_sdk/models/schemas/transports/mcp_tool_call.py diff --git a/.codespellignore b/.codespellignore index 4d75c60..e818214 100644 --- a/.codespellignore +++ b/.codespellignore @@ -2,3 +2,4 @@ mor tok fpan dpan +requestor diff --git a/.github/workflows/conventional-commits.yml b/.github/workflows/conventional-commits.yml index 4193d6f..928b10e 100644 --- a/.github/workflows/conventional-commits.yml +++ b/.github/workflows/conventional-commits.yml @@ -33,7 +33,8 @@ jobs: runs-on: ubuntu-latest steps: - name: semantic-pull-request - uses: amannn/action-semantic-pull-request@v6 + # zizmor: ignore[unpinned-uses] + uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index ce008fd..99a5613 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -20,15 +20,20 @@ on: pull_request: branches: [main] +permissions: + contents: read + jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - uses: actions/setup-python@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 with: python-version: '3.x' - - uses: pre-commit/action@v3.0.1 + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 env: # workaround for pre-commit/action not being updated to node24 FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9b11260..829dc01 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -34,6 +34,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Set up Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 with: @@ -49,14 +51,16 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Install uv uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: enable-cache: true - name: Regenerate models from the pinned spec version - # SDK 0.4.x targets UCP 2026-04-08 (see the README compatibility + # SDK 0.5.x targets UCP 2026-08-25 (see the README compatibility # table). Bump this pin together with the SDK version line. - run: ./generate_models.sh 2026-04-08 + run: ./generate_models.sh 2026-08-25 - name: Normalize file endings (as pre-commit's end-of-file-fixer does) run: | python3 - <<'PY' @@ -72,7 +76,7 @@ jobs: run: | git add -A -- src/ucp_sdk/models/schemas if ! git diff --cached --quiet -- src/ucp_sdk/models/schemas; then - echo "::error::Committed models differ from regeneration against the pinned UCP spec. Either the generation pipeline is broken, or the models were edited without regenerating. Run ./generate_models.sh 2026-04-08 and commit the result." + echo "::error::Committed models differ from regeneration against the pinned UCP spec. Either the generation pipeline is broken, or the models were edited without regenerating. Run ./generate_models.sh 2026-08-25 and commit the result." git --no-pager diff --cached --stat -- src/ucp_sdk/models/schemas git --no-pager diff --cached -- src/ucp_sdk/models/schemas exit 1 diff --git a/.gitignore b/.gitignore index 2874c4a..c490905 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ node_modules/ __pycache__/ .venv/ *.py[cod] +ucp/ +uv.lock diff --git a/README.md b/README.md index 9b609a3..83f5ece 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,8 @@ UCP schema: | SDK Version | UCP Schema Version | | ----------------- | ------------------ | -| **`0.4.x`** | **`2026-04-08`** | +| **`0.5.x`** | **`2026-08-25`** | +| `0.4.x` | `2026-04-08` | | `0.3.x` | `2026-01-23` | | `0.2.x` / `0.1.x` | `2026-01-11` | @@ -71,20 +72,22 @@ from ucp_sdk.models.schemas.shopping.checkout import Checkout checkout = Checkout.model_validate(checkout_data) # Access typed fields -print(checkout.status) # "incomplete" | "ready_for_complete" | ... -print(checkout.currency) # ISO 4217 currency code +print(checkout.status) # "incomplete" | "ready_for_complete" | ... +print(checkout.currency) # ISO 4217 currency code for item in checkout.line_items: print(f"{item.item.title}: {item.quantity}") ``` ### Available model packages -| Package | Description | -| --------------------------------------- | --------------------------------------------------- | -| `ucp_sdk.models.schemas.shopping` | Checkout, cart, catalog, order, payment models | -| `ucp_sdk.models.schemas.shopping.types` | Line items, totals, buyer, fulfillment, signals | -| `ucp_sdk.models.schemas.transports` | REST, MCP, and embedded protocol bindings | -| `ucp_sdk.models.schemas` | Service definitions, capabilities, payment handlers | +| Package | Description | +| --------------------------------------- | ----------------------------------------------------------------- | +| `ucp_sdk.models.schemas.common` | Location search/lookup, identity linking, loyalty, payment terms | +| `ucp_sdk.models.schemas.common.types` | Shared primitives (amounts, prices, totals, postal address, etc.) | +| `ucp_sdk.models.schemas.shopping` | Checkout, cart, catalog, order, buyer consent, permalink models | +| `ucp_sdk.models.schemas.shopping.types` | Line items, adjustments, fulfillments, destinations, attribution | +| `ucp_sdk.models.schemas.transports` | REST, MCP, and embedded protocol bindings | +| `ucp_sdk.models.schemas` | Service definitions, capabilities, profile, payment handlers | ### Validation @@ -133,7 +136,7 @@ uv sync ``` Where `` is the version of the UCP specification to use (for example, -"2026-04-08"). +"2026-08-25"). If no version is specified, the `main` branch of the [UCP repo](https://github.com/Universal-Commerce-Protocol/ucp) will be used. diff --git a/generate_models.sh b/generate_models.sh index 4e867ae..86d2b17 100755 --- a/generate_models.sh +++ b/generate_models.sh @@ -27,13 +27,6 @@ if ! command -v git &> /dev/null; then exit 1 fi -# Check if uv is installed before cloning or modifying generated files -if ! command -v uv &> /dev/null; then - echo "Error: uv not found." - echo "Please install uv: curl -LsSf https://astral.sh/uv/install.sh | sh" - exit 1 -fi - # UCP Version to use (if provided, use release/$1 branch; otherwise, use main) if [ -z "$1" ]; then BRANCH="main" @@ -46,6 +39,7 @@ fi # Ensure ucp directory is clean before cloning rm -rf ucp git clone -b "$BRANCH" --depth 1 https://github.com/Universal-Commerce-Protocol/ucp ucp +rm -rf ucp/.git # Output directory OUTPUT_DIR="src/ucp_sdk/models/schemas" @@ -101,9 +95,19 @@ uv run \ echo "Post-processing generated models (constraints the generator ignores)..." uv run python postprocess_models.py || exit 1 +# Normalize file endings (as pre-commit's end-of-file-fixer does) +python3 - <<'PY' +from pathlib import Path + +for path in Path("src/ucp_sdk/models/schemas").rglob("*.py"): + text = path.read_text(encoding="utf-8") + fixed = text.rstrip("\n") + "\n" if text.strip() else "" + if fixed != text: + path.write_text(fixed, encoding="utf-8") +PY + echo "Formatting generated models..." uv run ruff format uv run ruff check --fix "$OUTPUT_DIR" - echo "Done. Models generated in $OUTPUT_DIR" diff --git a/postprocess_models.py b/postprocess_models.py index 5043742..84d35e7 100644 --- a/postprocess_models.py +++ b/postprocess_models.py @@ -234,7 +234,7 @@ def find_root_min_properties(schema_dir): "cannot map to a class\n" ) continue - found[title] = minimum + found[_alias_name(title)] = minimum return found diff --git a/preprocess_schemas.py b/preprocess_schemas.py index a9bba53..e8e56be 100644 --- a/preprocess_schemas.py +++ b/preprocess_schemas.py @@ -141,15 +141,15 @@ def _process_all_of_item(item, node, root, state): # Extract polymorphic branches (anyOf, oneOf) to keep the node flat for poly_key in ["anyOf", "oneOf"]: - if poly_key in item: + if poly_key in item and isinstance(item[poly_key], list): state["poly_branches"].setdefault(poly_key, []).extend( item.pop(poly_key) ) # Merge core property definitions and requirements - if "properties" in item: + if "properties" in item and isinstance(item["properties"], dict): state["merged_properties"].update(item["properties"]) - if "required" in item: + if "required" in item and isinstance(item["required"], list): for req in item["required"]: if req not in state["merged_required"]: state["merged_required"].append(req) @@ -196,7 +196,7 @@ def merge_all_of_to_node(node, root): models if inheritance is flattened at the schema level rather than relying on complex 'allOf' chains which can lead to redundant intermediate classes. """ - if "allOf" not in node: + if "allOf" not in node or not isinstance(node["allOf"], list): return all_of_sources = node.pop("allOf") @@ -221,15 +221,19 @@ def distribute_properties_to_branches(node): model in Pydantic. Without this, a generated union model might miss required common fields if it's treated as a pure 'oneOf' alternative. """ - if "properties" not in node: + if "properties" not in node or not isinstance(node["properties"], dict): return base_props = node["properties"] - base_req = node.get("required", []) + base_req = ( + node.get("required", []) + if isinstance(node.get("required"), list) + else [] + ) base_type = node.get("type") for poly_key in ["anyOf", "oneOf"]: - if poly_key not in node: + if poly_key not in node or not isinstance(node[poly_key], list): continue updated_branches = [] @@ -263,7 +267,7 @@ def flatten_entity_reference(node, entity_definition): Replaces $ref to 'ucp.json#/$defs/entity' with actual logic. This effectively converts 'Entity' inheritance into direct 'BaseModel' fields. """ - if "allOf" not in node: + if "allOf" not in node or not isinstance(node["allOf"], list): return filtered_all_of = [] @@ -291,6 +295,10 @@ def preprocess_full_schema(schema, entity_def=None): Main entry point for normalizing a single schema file. Uses bottom-up iteration to ensure nested structures are flat before parents process them. """ + # Remove $id so datamodel-code-generator resolves relative $refs strictly + # via the local filesystem rather than attempting remote HTTP fetching. + schema.pop("$id", None) + # 1. Discovery: find all dictionaries in the tree nodes = [n for n in iter_nodes(schema) if isinstance(n, dict)] diff --git a/pyproject.toml b/pyproject.toml index 9de3e5c..64bc9eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,11 @@ [project] name = "ucp-sdk" -version = "0.4.6" +version = "0.5.0" description = "UCP Python SDK" readme = "README.md" license = {file = "LICENSE"} authors = [ + { name = "Enric Cusell", email = "cusell@google.com" }, { name = "Federico D'Amato", email = "damaz@google.com" } ] classifiers = [ @@ -26,14 +27,7 @@ dependencies = [ [dependency-groups] dev = [ - # Exact pins: the model-drift CI job regenerates the models against the - # pinned UCP spec and asserts byte-identical output, so the codegen - # toolchain must resolve reproducibly. (The floating ">=0.50.0" bound - # broke on 2026-08-03: datamodel-code-generator 0.72.0 mis-resolves the - # spec's remote $refs and emits incomplete models.) Bump these pins and - # regenerate the models in the same PR. - "datamodel-code-generator[http]==0.71.0", - "ruff==0.16.1", + "datamodel-code-generator[http,ruff]>=0.50.0", ] [build-system] @@ -75,7 +69,10 @@ skip-magic-trailing-comma = false line-ending = "auto" [tool.ruff.lint.per-file-ignores] -"src/ucp_sdk/models/schemas/**/*.py" = ["E501", "D", "N801"] +"src/ucp_sdk/models/schemas/**/*.py" = ["E501", "D", "N801", "N815"] +"preprocess_schemas.py" = ["E501", "D"] +"postprocess_models.py" = ["E501", "D"] +"tests/**/*.py" = ["E501", "D"] [tool.ruff.lint] select = ["E", "F", "W", "B", "C4", "SIM", "N", "UP", "D", "PTH", "T20"] diff --git a/src/ucp_sdk/__init__.py b/src/ucp_sdk/__init__.py new file mode 100644 index 0000000..2699c74 --- /dev/null +++ b/src/ucp_sdk/__init__.py @@ -0,0 +1,17 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""UCP Python SDK.""" + +__version__ = "0.5.0" diff --git a/src/ucp_sdk/models/__init__.py b/src/ucp_sdk/models/__init__.py new file mode 100644 index 0000000..bb24223 --- /dev/null +++ b/src/ucp_sdk/models/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""UCP models.""" diff --git a/src/ucp_sdk/models/schemas/capability.py b/src/ucp_sdk/models/schemas/capability.py index 1a53ae0..3538563 100644 --- a/src/ucp_sdk/models/schemas/capability.py +++ b/src/ucp_sdk/models/schemas/capability.py @@ -23,6 +23,8 @@ from pydantic import AnyUrl, BaseModel, ConfigDict, Field from typing_extensions import TypeAliasType +from .common.types import reverse_domain_name + UcpCapability = TypeAliasType( "UcpCapability", Annotated[Any, Field(..., title="UCP Capability")] ) @@ -34,7 +36,7 @@ Extends = TypeAliasType( "Extends", Annotated[ - str, Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") + list[reverse_domain_name.ReverseDomainName], Field(..., min_length=1) ], ) """ @@ -42,22 +44,6 @@ """ -Extends1Item = TypeAliasType( - "Extends1Item", - Annotated[ - str, Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") - ], -) - - -Extends1 = TypeAliasType( - "Extends1", Annotated[list[Extends1Item], Field(..., min_length=1)] -) -""" -Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. -""" - - class Base(BaseModel): model_config = ConfigDict( extra="allow", @@ -82,26 +68,12 @@ class Base(BaseModel): """ Entity-specific configuration. Structure defined by each entity's schema. """ - extends: Extends | Extends1 | None = None + extends: reverse_domain_name.ReverseDomainName | Extends | None = None """ Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. """ -Extends2 = TypeAliasType("Extends2", Extends) - - -Extends3Item = TypeAliasType("Extends3Item", Extends1Item) - - -Extends3 = TypeAliasType( - "Extends3", Annotated[list[Extends3Item], Field(..., min_length=1)] -) -""" -Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. -""" - - class PlatformSchema(BaseModel): """ Full capability declaration for platform-level discovery. Includes spec/schema URLs for agent fetching. @@ -130,29 +102,15 @@ class PlatformSchema(BaseModel): """ Entity-specific configuration. Structure defined by each entity's schema. """ - extends: Extends2 | Extends3 | None = None + extends: reverse_domain_name.ReverseDomainName | Extends | None = None """ Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. """ -Extends4 = TypeAliasType("Extends4", Extends) - - -Extends5Item = TypeAliasType("Extends5Item", Extends1Item) - - -Extends5 = TypeAliasType( - "Extends5", Annotated[list[Extends5Item], Field(..., min_length=1)] -) -""" -Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. -""" - - class BusinessSchema(BaseModel): """ - Capability configuration for business/merchant level. May include business-specific config overrides. + Capability declaration for business/merchant discovery. Requires the `schema` URL so platforms can fetch and compose it during negotiation; may also include business-specific config overrides. """ model_config = ConfigDict( @@ -166,7 +124,7 @@ class BusinessSchema(BaseModel): """ URL to human-readable specification document. """ - schema_: AnyUrl | None = Field(None, alias="schema") + schema_: AnyUrl = Field(..., alias="schema") """ URL to JSON Schema defining this entity's structure and payloads. """ @@ -178,26 +136,12 @@ class BusinessSchema(BaseModel): """ Entity-specific configuration. Structure defined by each entity's schema. """ - extends: Extends4 | Extends5 | None = None + extends: reverse_domain_name.ReverseDomainName | Extends | None = None """ Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. """ -Extends6 = TypeAliasType("Extends6", Extends) - - -Extends7Item = TypeAliasType("Extends7Item", Extends1Item) - - -Extends7 = TypeAliasType( - "Extends7", Annotated[list[Extends7Item], Field(..., min_length=1)] -) -""" -Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. -""" - - class ResponseSchema(BaseModel): """ Capability reference in responses. Only name/version required to confirm active capabilities. @@ -226,7 +170,7 @@ class ResponseSchema(BaseModel): """ Entity-specific configuration. Structure defined by each entity's schema. """ - extends: Extends6 | Extends7 | None = None + extends: reverse_domain_name.ReverseDomainName | Extends | None = None """ Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. """ diff --git a/src/ucp_sdk/models/schemas/common/identity_linking.py b/src/ucp_sdk/models/schemas/common/identity_linking.py index 5b32ea4..aebf51c 100644 --- a/src/ucp_sdk/models/schemas/common/identity_linking.py +++ b/src/ucp_sdk/models/schemas/common/identity_linking.py @@ -23,7 +23,7 @@ from pydantic import BaseModel, ConfigDict, Field from typing_extensions import TypeAliasType -from ..shopping.types import description as description_1 +from .types import description as description_1 IdentityLinking = TypeAliasType( "IdentityLinking", Annotated[Any, Field(..., title="Identity Linking")] @@ -53,7 +53,7 @@ class ScopePolicy(BaseModel): str, Field( ..., - pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+:[a-z][a-z0-9_]*$", + pattern="^[a-z](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9_-]*[a-z0-9_])?)+:[a-z][a-z0-9_]*$", ), ], ) @@ -62,4 +62,18 @@ class ScopePolicy(BaseModel): """ +class Provider(BaseModel): + """ + A trusted identity provider for delegated authentication, keyed by the 'type' discriminator. 'oauth2' denotes an OAuth 2.0 / OIDC authorization server. Future versions MAY define additional types (e.g. wallet attestation) as non-breaking extensions; platforms MUST treat entries whose 'type' they do not support as filtered out (see Provider Selection). + """ + + model_config = ConfigDict( + extra="allow", + ) + type: str + """ + Provider mechanism discriminator. 'oauth2' for OAuth 2.0 / OIDC authorization servers. Additional values MAY be defined by future versions; the value is an open string, not a closed enum, so unrecognized types remain valid and are filtered at runtime. + """ + + IdentityLinking1 = TypeAliasType("IdentityLinking1", Any) diff --git a/src/ucp_sdk/models/schemas/common/location_lookup.py b/src/ucp_sdk/models/schemas/common/location_lookup.py new file mode 100644 index 0000000..39c2714 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/location_lookup.py @@ -0,0 +1,101 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + +from .. import ucp as ucp_1 +from .types import context as context_1 +from .types import location_distance, location_filter, location_serves, message +from .types import signals as signals_1 +from .types.location import Location + + +class LocationLookup(BaseModel): + """ + Location lookup by identifiers. Supports batch retrieval and single-location detail. + """ + + model_config = ConfigDict( + extra="allow", + ) + + +class Input(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + The identifier exactly as supplied in the lookup request. + """ + + +class LookupRequest(BaseModel): + """ + Request body for batch location lookup. The Business resolves and deduplicates `ids` before applying `distance`, `serves`, and every supplied `filters` predicate; all structured predicates combine with AND. + """ + + model_config = ConfigDict( + extra="allow", + ) + ids: list[str] = Field(..., min_length=1) + """ + Identifiers of the Locations to look up. The Business MUST support canonical `Location.id` values and MAY support secondary or alias identifiers. + """ + distance: location_distance.LocationDistance | None = None + """ + Optional explicit-center radius predicate applied after ID resolution. It combines with `serves` and every supplied `filters` predicate using AND. + """ + serves: location_serves.LocationServes | None = None + """ + Optional authoritative service-target predicate applied after ID resolution. It combines with `distance` and every supplied `filters` predicate using AND. + """ + filters: location_filter.LocationFilter | None = None + context: context_1.Context | None = None + signals: signals_1.Signals | None = None + + +class LookupLocation(Location): + """ + Location with required correlation metadata for lookup responses. + """ + + model_config = ConfigDict( + extra="allow", + ) + inputs: list[Input] = Field(..., min_length=1) + """ + Which request identifiers resolved to this Location. Each entry preserves one identifier exactly as supplied in the request. + """ + + +class LookupResponse(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + ucp: ucp_1.ResponseLocationSchema + locations: list[LookupLocation] + """ + Locations matching the requested identifiers and refinements. May contain fewer Locations if some identifiers do not resolve or their resolved Locations are filtered out, or more if one identifier resolves to multiple Locations. When multiple identifiers resolve to the same Location, one returned Location carries all corresponding `inputs` entries. + """ + messages: list[message.Message] | None = None + """ + Errors, warnings, or informational messages about the requested Locations, including `batch_limit_applied` when the Business processes only its configured maximum number of identifiers. + """ diff --git a/src/ucp_sdk/models/schemas/common/location_search.py b/src/ucp_sdk/models/schemas/common/location_search.py new file mode 100644 index 0000000..35e0983 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/location_search.py @@ -0,0 +1,85 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + +from .. import ucp as ucp_1 +from .types import context as context_1 +from .types import ( + location, + location_distance, + location_filter, + location_serves, + message, +) +from .types import pagination as pagination_1 +from .types import signals as signals_1 + + +class LocationSearch(BaseModel): + """ + Location search capability. Supports natural language queries, distance and serviceability relations, structured filtering including current item availability, and pagination. + """ + + model_config = ConfigDict( + extra="allow", + ) + + +class SearchRequest(BaseModel): + """ + Request body for location search. The `distance` and `serves` relations and every supplied `filters` predicate combine with AND; `query` does not relax them. + """ + + model_config = ConfigDict( + extra="allow", + ) + query: str | None = None + """ + Free-text search query for natural language location search (e.g., 'restaurants near me that deliver', 'hotels with pool'). + """ + context: context_1.Context | None = None + signals: signals_1.Signals | None = None + distance: location_distance.LocationDistance | None = None + """ + Optional explicit-center radius predicate. When present, it combines with `serves` and every supplied `filters` predicate using AND. + """ + serves: location_serves.LocationServes | None = None + """ + Optional authoritative service-target predicate. When present, it combines with `distance` and every supplied `filters` predicate using AND. + """ + filters: location_filter.LocationFilter | None = None + pagination: pagination_1.Request | None = None + + +class SearchResponse(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + ucp: ucp_1.ResponseLocationSchema + locations: list[location.Location] + """ + Locations matching the search criteria. + """ + pagination: pagination_1.Response | None = None + messages: list[message.Message] | None = None + """ + Errors, warnings, or informational messages about the search results. + """ diff --git a/src/ucp_sdk/models/schemas/common/loyalty.py b/src/ucp_sdk/models/schemas/common/loyalty.py new file mode 100644 index 0000000..009222d --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/loyalty.py @@ -0,0 +1,297 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Annotated, Any + +from pydantic import BaseModel, ConfigDict, Field +from typing_extensions import TypeAliasType + +from ..shopping.cart import Cart as Cart_1 +from ..shopping.catalog_lookup import GetProductResponse, LookupResponse +from ..shopping.catalog_search import SearchResponse +from ..shopping.checkout import Checkout as Checkout_1 +from .types import reverse_domain_name + +LoyaltyExtension = TypeAliasType( + "LoyaltyExtension", Annotated[Any, Field(..., title="Loyalty Extension")] +) +""" +Extends various Capabilities with loyalty support using memberships info. +""" + + +RewardAmount = TypeAliasType("RewardAmount", Annotated[int, Field(..., ge=0)]) +""" +Non-negative integer amount denominated in the minor unit of the associated reward currency. The associated reward currency's `decimal_places` defines the minor-to-major ratio and defaults to 0 when omitted. +""" + + +class EarningBreakdown(BaseModel): + """ + Breakdown rule of the reward earnings + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + Unique rewards breakdown rule identifier. + """ + amount: RewardAmount + """ + Rewards earned from this rule. + """ + description: str + """ + A display-ready, human-readable rationale for the specific rewards (e.g. 2x on footwear). + """ + benefit_id: str | None = None + """ + Optional `id` of the membership_tier_benefit that produced this rewards rule. Resolves against `membership_tier_benefit.id` within the same parent loyalty membership. + """ + + +class EarningForecast(BaseModel): + """ + Preview of rewards to be earned from the current transaction. + """ + + model_config = ConfigDict( + extra="allow", + ) + amount: RewardAmount + """ + Total rewards to be earned if the transaction completes. + """ + breakdown: list[EarningBreakdown] | None = None + """ + List of breakdown of earning contributing to the total. + """ + + +class RewardCurrency(BaseModel): + """ + The currency of the loyalty reward. + """ + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + Human-readable name of the currency (e.g. 'LoyaltyStars'). + """ + code: str + """ + Business-specific representation of the currency (e.g. 'LST'). + """ + decimal_places: int | None = Field(0, ge=0) + """ + The position of a digit to the right of a decimal point. Applies to all amount related fields for rewards. + """ + + +class Currency(BaseModel): + """ + A unit of value that customers can accumulate through various commercial activities. + """ + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + Human-readable name of the currency (e.g. 'LoyaltyStars'). + """ + code: str + """ + Business-specific representation of the currency (e.g. 'LST'). + """ + decimal_places: int | None = Field(0, ge=0) + """ + The position of a digit to the right of a decimal point. Applies to all amount related fields for rewards. + """ + + +class MembershipReward(BaseModel): + """ + Quantifiable reward type and optional earning forecast for the current transaction. + """ + + model_config = ConfigDict( + extra="allow", + ) + currency: Currency + """ + A unit of value that customers can accumulate through various commercial activities. + """ + earning_forecast: EarningForecast | None = None + """ + Preview of rewards to be earned from the current transaction. + """ + + +class MembershipTierBenefit(BaseModel): + """ + Benefits associated with a membership tier. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + Unique identifier for the tier benefit. + """ + description: str + """ + A display-ready, human-readable explanation of this benefit (e.g. 'Early access to sales'). + """ + + +class MembershipTier(BaseModel): + """ + Specific achievement rank or status milestone that unlocks escalating value as a member progresses through activity or spend. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + Unique identifier for the membership tier. + """ + name: str + """ + The human-readable name of the tier (e.g., 'Platinum'). + """ + benefits: list[MembershipTierBenefit] | None = None + """ + List of benefits associated with this tier. + """ + + +class LoyaltyMembership(BaseModel): + """ + Loyalty membership the business has accepted for the eligibility claim represented by the parent map key. Programs that can be joined independently MUST be modeled as separate sibling entries under the loyalty map, distinguished by reverse-domain naming (e.g., 'com.example.rewards' and 'com.example.rewards.card'). + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + Unique loyalty membership identifier. + """ + name: str + """ + Business specific name of the loyalty membership/program. + """ + display_id: str | None = None + """ + A masked or partial version of the membership id for user recognition (e.g., '****5678'). MUST NOT be set if the membership has not been verified. + """ + tiers: list[MembershipTier] | None = None + """ + Active or display-safe tier context for this membership. Most programs are single-status (one entry); programs with parallel status dimensions (e.g., current and lifetime) populate one entry per active tier. Omitted when no tier context has been resolved. + """ + rewards: list[MembershipReward] | None = None + """ + Reward types and earning forecasts associated with this membership. Each object encapsulates one type of reward. + """ + provisional: bool + """ + True if this membership requires additional verification. + """ + + +Loyalty = TypeAliasType( + "Loyalty", dict[reverse_domain_name.ReverseDomainName, LoyaltyMembership] +) +""" +Key-value map whose keys represent buyer/platform asserted eligibility claims and whose values represent associated membership information. All loyalty keys MUST use reverse-domain naming to ensure provenance and prevent collisions when multiple extensions contribute to the shared namespace. +""" + + +class Search(SearchResponse): + """ + Catalog Search response extended with Loyalty capability. + """ + + model_config = ConfigDict( + extra="allow", + ) + loyalty: Loyalty | None = None + + +class Lookup1(LookupResponse): + """ + Catalog Lookup response extended with Loyalty capability. + """ + + model_config = ConfigDict( + extra="allow", + ) + loyalty: Loyalty | None = None + + +class Lookup2(GetProductResponse): + """ + Catalog Lookup response extended with Loyalty capability. + """ + + model_config = ConfigDict( + extra="allow", + ) + loyalty: Loyalty | None = None + + +Lookup = TypeAliasType( + "Lookup", + Annotated[ + Lookup1 | Lookup2, Field(..., title="Catalog Lookup with Loyalty") + ], +) +""" +Catalog Lookup response extended with Loyalty capability. +""" + + +class Cart(Cart_1): + """ + Cart extended with Loyalty capability. + """ + + model_config = ConfigDict( + extra="allow", + ) + loyalty: Loyalty | None = None + + +class Checkout(Checkout_1): + """ + Checkout extended with Loyalty capability. + """ + + model_config = ConfigDict( + extra="allow", + ) + loyalty: Loyalty | None = None diff --git a/src/ucp_sdk/models/schemas/shopping/ap2_mandate.py b/src/ucp_sdk/models/schemas/common/payment_ap2_mandate.py similarity index 94% rename from src/ucp_sdk/models/schemas/shopping/ap2_mandate.py rename to src/ucp_sdk/models/schemas/common/payment_ap2_mandate.py index 49b65ba..d8406b4 100644 --- a/src/ucp_sdk/models/schemas/shopping/ap2_mandate.py +++ b/src/ucp_sdk/models/schemas/common/payment_ap2_mandate.py @@ -23,11 +23,11 @@ from pydantic import BaseModel, ConfigDict, Field from typing_extensions import TypeAliasType -from .checkout import Checkout as Checkout_1 +from ..shopping.checkout import Checkout as Checkout_1 -Ap2MandateExtension = TypeAliasType( - "Ap2MandateExtension", - Annotated[Any, Field(..., title="AP2 Mandate Extension")], +PaymentAp2MandateExtension = TypeAliasType( + "PaymentAp2MandateExtension", + Annotated[Any, Field(..., title="Payment AP2 Mandate Extension")], ) """ Extends Checkout with cryptographic mandate support for non-repudiable authorization per the AP2 protocol. Uses embedded signature model with ap2 namespace. diff --git a/src/ucp_sdk/models/schemas/common/payment_authentication.py b/src/ucp_sdk/models/schemas/common/payment_authentication.py new file mode 100644 index 0000000..2fa2e90 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/payment_authentication.py @@ -0,0 +1,101 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Annotated, Any + +from pydantic import AnyUrl, BaseModel, ConfigDict, Field +from typing_extensions import TypeAliasType + +from ..shopping.checkout import Checkout as Checkout_1 + +PaymentAuthenticationExtension = TypeAliasType( + "PaymentAuthenticationExtension", + Annotated[Any, Field(..., title="Payment Authentication Extension")], +) +""" +Extends capabilities (e.g., checkout in retail shopping) with standard device data collection and 3DS challenge Action types used during payment authentication. +""" + + +class Config(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + payment_instrument_id: str = Field(..., min_length=1) + """ + ID of the payment instrument in the containing Checkout associated with this device data collection Action. + """ + url: AnyUrl + """ + URL for the invisible device data collection surface. + """ + + +class DevUcpCommonPaymentDeviceDataCollectionItem(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + config: Config + + +class Config1(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + payment_instrument_id: str = Field(..., min_length=1) + """ + ID of the payment instrument in the containing Checkout associated with this 3DS challenge Action. + """ + url: AnyUrl + """ + URL for the buyer-facing 3DS challenge surface. + """ + + +class DevUcpCommonPaymentThreeDsChallengeItem(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + config: Config1 + + +class Actions(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + dev_ucp_common_payment_device_data_collection: ( + list[DevUcpCommonPaymentDeviceDataCollectionItem] | None + ) = Field(None, alias="dev.ucp.common.payment.device_data_collection") + """ + A 3DS device data collection Action. + """ + dev_ucp_common_payment_three_ds_challenge: ( + list[DevUcpCommonPaymentThreeDsChallengeItem] | None + ) = Field(None, alias="dev.ucp.common.payment.three_ds_challenge") + """ + A 3DS challenge Action. + """ + + +class Checkout(Checkout_1): + model_config = ConfigDict( + extra="allow", + ) + actions: Actions | None = None diff --git a/src/ucp_sdk/models/schemas/common/payment_split_payments.py b/src/ucp_sdk/models/schemas/common/payment_split_payments.py new file mode 100644 index 0000000..3551041 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/payment_split_payments.py @@ -0,0 +1,80 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Annotated, Any + +from pydantic import BaseModel, ConfigDict, Field +from typing_extensions import TypeAliasType + +from ..shopping.checkout import Checkout as Checkout_1 +from .types import amount as amount_1 +from .types import instrument_group +from .types.payment_instrument import PaymentInstrument as PaymentInstrument_1 + +PaymentSplitPaymentsExtension = TypeAliasType( + "PaymentSplitPaymentsExtension", + Annotated[Any, Field(..., title="Payment Split Payments Extension")], +) +""" +Enables Buyers to use multiple payment instruments for a single session. +""" + + +InstrumentGroup = TypeAliasType( + "InstrumentGroup", instrument_group.InstrumentGroup +) + + +SplitPayments = TypeAliasType("SplitPayments", Any) + + +class PaymentInstrument(PaymentInstrument_1): + """ + Payment instrument extended with an optional per-instrument amount for split payments. + """ + + model_config = ConfigDict( + extra="allow", + ) + amount: amount_1.Amount | None = None + """ + Contribution amount for this instrument expressed in ISO 4217 minor units of the containing capability object's `currency`. On request: the platform's requested contribution (omit for open-amount). On response: the actual amount authorized or charged (omitted when not finally processed). + """ + + +class Payment(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + instruments: list[PaymentInstrument] | None = None + """ + Payment instruments in allocation priority order. + """ + + +class Checkout(Checkout_1): + """ + Checkout extended with split payment instrument amounts. + """ + + model_config = ConfigDict( + extra="allow", + ) + payment: Payment | None = None diff --git a/src/ucp_sdk/models/schemas/common/payment_terms.py b/src/ucp_sdk/models/schemas/common/payment_terms.py new file mode 100644 index 0000000..1e2d6ee --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/payment_terms.py @@ -0,0 +1,99 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Annotated, Any + +from pydantic import BaseModel, ConfigDict, Field +from typing_extensions import TypeAliasType + +from ..shopping.checkout import Checkout as Checkout_1 +from ..shopping.order import Order as Order_1 +from .types import payment_term + +PaymentTermsExtension = TypeAliasType( + "PaymentTermsExtension", + Annotated[Any, Field(..., title="Payment Terms Extension")], +) +""" +Extends capabilities (e.g., checkout in retail shopping) with selectable payment terms, letting a Business offer alternative schedules for when payment for the checkout is due, and projects the accepted term onto the resulting Order. +""" + + +PaymentTerm = TypeAliasType("PaymentTerm", payment_term.PaymentTerm) + + +class Payment(BaseModel): + """ + Payment object extended with selectable payment terms. + """ + + model_config = ConfigDict( + extra="allow", + ) + terms: list[PaymentTerm] | None = Field(None, min_length=1) + """ + Payment terms the Buyer can choose from. An unselected term's amounts are indicative; the selected term's schedule amounts sum to the checkout total. + """ + selected_term_id: str | None = None + """ + ID of the selected payment term. MUST match one `terms[].id` from the latest Checkout response. Present in a response whenever `terms` is, and absent when it is not: the Checkout total is the selected term's total, so a list of terms without a selection would show an amount that matches no stated term. Where the Buyer has made no choice, the Business selects a default. Omitted on create requests because term IDs are checkout-scoped and no terms exist yet, and on complete requests because the term is already agreed by then. Selecting a term is an Update Checkout mutation: the Business response is authoritative for all derived state. + """ + + +class OrderPayment(BaseModel): + """ + Order payment details carrying the term the Buyer accepted at checkout. + """ + + model_config = ConfigDict( + extra="allow", + ) + accepted_term: PaymentTerm | None = None + """ + The payment term the Buyer accepted at checkout. Businesses MUST carry it forward so the Order states the amounts owed and when, and MUST ensure its schedule amounts sum to the Order total. The available terms are checkout state and are not projected. + """ + + +class Checkout(Checkout_1): + """ + Checkout extended with selectable payment terms. + """ + + model_config = ConfigDict( + extra="allow", + ) + payment: Payment | None = None + """ + Payment details with available and selected payment terms. + """ + + +class Order(Order_1): + """ + Order extended with the payment term accepted at checkout. + """ + + model_config = ConfigDict( + extra="allow", + ) + payment: OrderPayment | None = None + """ + Payment details for the Order, including the accepted payment term. + """ diff --git a/src/ucp_sdk/models/schemas/common/types/__init__.py b/src/ucp_sdk/models/schemas/common/types/__init__.py new file mode 100644 index 0000000..1252d6b --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/__init__.py @@ -0,0 +1,17 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable diff --git a/src/ucp_sdk/models/schemas/common/types/actions.py b/src/ucp_sdk/models/schemas/common/types/actions.py new file mode 100644 index 0000000..d67b151 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/actions.py @@ -0,0 +1,56 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Annotated, Any + +from pydantic import BaseModel, ConfigDict, Field +from typing_extensions import TypeAliasType + +from . import reverse_domain_name + + +class Instance(BaseModel): + """ + Common fields for one outstanding Action instance are id and optional config. The extension declaring the Action type defines type-specific processing data under config. Additional properties are permitted for forward compatibility. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str = Field(..., min_length=1) + """ + Identifier for this Action instance. + """ + config: dict[str, Any] | None = None + """ + Configuration defined by the extension that declares this Action type. + """ + + +Actions = TypeAliasType( + "Actions", + Annotated[ + dict[reverse_domain_name.ReverseDomainName, list[Instance]], + Field(..., title="Actions"), + ], +) +""" +Outstanding extension-defined Action instances, keyed by reverse-domain Action type, not extension name. +""" diff --git a/src/ucp_sdk/models/schemas/common/types/amenity_type.py b/src/ucp_sdk/models/schemas/common/types/amenity_type.py new file mode 100644 index 0000000..cdf4b55 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/amenity_type.py @@ -0,0 +1,46 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Annotated + +from pydantic import Field +from typing_extensions import TypeAliasType + +from . import reverse_domain_name + +AmenityType = TypeAliasType( + "AmenityType", + Annotated[ + reverse_domain_name.ReverseDomainName, + Field( + ..., + examples=[ + "dev.ucp.amenity.wi_fi", + "dev.ucp.amenity.parking", + "dev.ucp.amenity.shopping.in_store_pickup", + "dev.ucp.amenity.shopping.curbside_pickup", + ], + title="Amenity Type", + ), + ], +) +""" +A standardized open reverse-DNS string representing a physical feature, capability, or service provided by a location. Supports a 2-tier hierarchy when defining well-known values in UCP: 1) Common vocabulary for universal features that directly live on `dev.ucp.amenity` (e.g., dev.ucp.amenity.wi_fi) and 2) Industry-scoped features that MUST append their related service name on top of `dev.ucp.amenity` (e.g., dev.ucp.amenity.shopping.in_store_pickup). Businesses MAY define custom vocabulary in their own domain (e.g., com.example.amenity.auto_care_center). +""" diff --git a/src/ucp_sdk/models/schemas/shopping/types/amount.py b/src/ucp_sdk/models/schemas/common/types/amount.py similarity index 91% rename from src/ucp_sdk/models/schemas/shopping/types/amount.py rename to src/ucp_sdk/models/schemas/common/types/amount.py index 0715e52..f4d901d 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/amount.py +++ b/src/ucp_sdk/models/schemas/common/types/amount.py @@ -24,7 +24,8 @@ from typing_extensions import TypeAliasType Amount = TypeAliasType( - "Amount", Annotated[int, Field(..., ge=0, title="Amount")] + "Amount", + Annotated[int, Field(..., ge=0, le=9007199254740991, title="Amount")], ) """ Monetary amount in the currency's minor unit as defined by ISO 4217. Refer to the currency's exponent to determine minor-to-major ratio (e.g., 2 for USD, 0 for JPY, 3 for KWD). diff --git a/src/ucp_sdk/models/schemas/shopping/types/available_payment_instrument.py b/src/ucp_sdk/models/schemas/common/types/available_payment_instrument.py similarity index 70% rename from src/ucp_sdk/models/schemas/shopping/types/available_payment_instrument.py rename to src/ucp_sdk/models/schemas/common/types/available_payment_instrument.py index dda328c..237df44 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/available_payment_instrument.py +++ b/src/ucp_sdk/models/schemas/common/types/available_payment_instrument.py @@ -18,9 +18,9 @@ from __future__ import annotations -from typing import Any +from pydantic import BaseModel, ConfigDict -from pydantic import BaseModel, ConfigDict, Field +from . import constraint_expression class AvailablePaymentInstrument(BaseModel): @@ -35,7 +35,7 @@ class AvailablePaymentInstrument(BaseModel): """ The instrument type identifier (e.g., 'card', 'gift_card'). References an instrument schema's type constant. """ - constraints: dict[str, Any] | None = Field(None, min_length=1) + constraints: constraint_expression.ConstraintExpression | None = None """ - Constraints on this instrument type. Structure depends on instrument type and active capabilities. + A Constraint Expression describing the instrument this entry makes available. Keys in `properties` name members of the `constraint_target` declared by the instrument schema for this `type`. Requirements on submitted request data belong in `ucp.request_constraints` instead. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/binding.py b/src/ucp_sdk/models/schemas/common/types/binding.py similarity index 60% rename from src/ucp_sdk/models/schemas/shopping/types/binding.py rename to src/ucp_sdk/models/schemas/common/types/binding.py index 16f6961..021401e 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/binding.py +++ b/src/ucp_sdk/models/schemas/common/types/binding.py @@ -18,24 +18,24 @@ from __future__ import annotations -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, Field -from . import payment_identity +from . import reverse_domain_name class Binding(BaseModel): """ - Binds a token to a specific checkout session and participant. Prevents token reuse across different checkouts or participants. + Binds a credential or token to a specific capability resource. Prevents reuse across different resources. """ model_config = ConfigDict( extra="allow", ) - checkout_id: str + type: reverse_domain_name.ReverseDomainName """ - The checkout session identifier this token is bound to. + The capability that owns the bound resource, for example dev.ucp.shopping.checkout. MUST be a capability name declared in the UCP namespace. """ - identity: payment_identity.PaymentIdentity | None = None + id: str = Field(..., min_length=1) """ - The participant this token is bound to. Required when acting on behalf of another participant (e.g., agent tokenizing for merchant). Omit when the authenticated caller is the binding target. + Opaque identifier of the bound resource within the owning capability, for example a checkout identifier. """ diff --git a/src/ucp_sdk/models/schemas/common/types/business_split_payments_config.py b/src/ucp_sdk/models/schemas/common/types/business_split_payments_config.py new file mode 100644 index 0000000..0f87555 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/business_split_payments_config.py @@ -0,0 +1,48 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Annotated + +from pydantic import BaseModel, ConfigDict, Field +from typing_extensions import TypeAliasType + +from . import instrument_group + +AllowedCombination = TypeAliasType( + "AllowedCombination", + Annotated[list[instrument_group.InstrumentGroup], Field(..., min_length=1)], +) +""" +A single valid combination: an array of instrument groups that together define the constraints. All groups must be satisfied (AND logic). +""" + + +class BusinessSplitPaymentsConfig(BaseModel): + """ + Business-level configuration for split payments. Declaring the capability means multiple payment instruments are supported; this config declares which combinations are valid. + """ + + model_config = ConfigDict( + extra="allow", + ) + allowed_combinations: list[AllowedCombination] = Field(..., min_length=1) + """ + Array of valid instrument combinations. Each combination is an array of instrument groups. A payment is valid if it matches any combination. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/card_credential.py b/src/ucp_sdk/models/schemas/common/types/card_credential.py similarity index 73% rename from src/ucp_sdk/models/schemas/shopping/types/card_credential.py rename to src/ucp_sdk/models/schemas/common/types/card_credential.py index 43b231a..b7f671a 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/card_credential.py +++ b/src/ucp_sdk/models/schemas/common/types/card_credential.py @@ -27,7 +27,7 @@ class CardCredential(PaymentCredential): """ - A card credential containing sensitive payment card details including raw Primary Account Numbers (PANs). This credential type MUST NOT be used for checkout, only with payment handlers that tokenize or encrypt credentials. CRITICAL: Both parties handling CardCredential (sender and receiver) MUST be PCI DSS compliant. Transmission MUST use HTTPS/TLS with strong cipher suites. + Deprecated: use PAN Credential (`pan_credential.json`) or Network Token Credential (`network_token_credential.json`). A card credential containing sensitive payment card details including raw Primary Account Numbers (PANs). This credential type MUST NOT be used for checkout, only with payment handlers that tokenize or encrypt credentials. CRITICAL: Both parties handling CardCredential (sender and receiver) MUST be PCI DSS compliant. Transmission MUST use HTTPS/TLS with strong cipher suites. """ model_config = ConfigDict( @@ -39,7 +39,7 @@ class CardCredential(PaymentCredential): """ card_number_type: Literal["fpan", "network_token", "dpan"] """ - The type of card number. Network tokens are preferred with fallback to FPAN. See PCI Scope for more details. + Deprecated: the credential type now carries this distinction. The type of card number. Network tokens are preferred with fallback to FPAN. See PCI Scope for more details. """ number: str | None = Field(None, examples=["4242424242424242"]) """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/card_payment_instrument.py b/src/ucp_sdk/models/schemas/common/types/card_payment_instrument.py similarity index 66% rename from src/ucp_sdk/models/schemas/shopping/types/card_payment_instrument.py rename to src/ucp_sdk/models/schemas/common/types/card_payment_instrument.py index e75ecd4..ea5ba62 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/card_payment_instrument.py +++ b/src/ucp_sdk/models/schemas/common/types/card_payment_instrument.py @@ -20,9 +20,8 @@ from typing import Literal -from pydantic import AnyUrl, BaseModel, ConfigDict, Field, field_validator +from pydantic import AnyUrl, BaseModel, ConfigDict, Field -from .available_payment_instrument import AvailablePaymentInstrument from .payment_instrument import PaymentInstrument @@ -36,7 +35,7 @@ class Display(BaseModel): ) brand: str | None = None """ - The card brand/network (e.g., visa, mastercard, amex). + The card brand/network shown to the buyer (e.g., visa, mastercard, amex). Presentational only. """ last_digits: str | None = None """ @@ -60,40 +59,20 @@ class Display(BaseModel): """ -class Constraints(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - brands: list[str] | None = Field(None, min_length=1) +class ConstraintTarget(BaseModel): """ - Limit to specific card brands (e.g., ['visa', 'mastercard', 'amex']). - """ - - @field_validator("brands", mode="after") - def _enforce_unique_items_brands(cls, value): # noqa: N805 - """JSON Schema uniqueItems: reject duplicate entries.""" - if value is None: - return value - seen = [] - for item in value: - if item in seen: - raise ValueError( - "Items must be unique (schema uniqueItems=true)" - ) - seen.append(item) - return value - - -class AvailableCardPaymentInstrument(AvailablePaymentInstrument): - """ - Declares card instrument availability with card-specific constraints. + The object an available card instrument's `constraints` describes. It declares the constrainable members and their types and is never carried in a payload. """ model_config = ConfigDict( extra="allow", ) - type: Literal["card"] | None = None - constraints: Constraints | None = None + brand: str | None = Field( + None, examples=["visa", "mastercard", "cartebancaire"] + ) + """ + Card scheme. Derived from the account number, not submitted. + """ class CardPaymentInstrument(PaymentInstrument): @@ -108,6 +87,10 @@ class CardPaymentInstrument(PaymentInstrument): """ Indicates this is a card payment instrument. """ + network: str | None = None + """ + Card network elected for this transaction, typically a co-badged selection. When present, the business MAY decline if the card cannot route over it and MUST NOT substitute another. + """ display: Display | None = None """ Display information for this card payment instrument. diff --git a/src/ucp_sdk/models/schemas/common/types/constraint_expression.py b/src/ucp_sdk/models/schemas/common/types/constraint_expression.py new file mode 100644 index 0000000..236ae50 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/constraint_expression.py @@ -0,0 +1,101 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field, field_validator +from typing_extensions import TypeAliasType + + +class ValueConstraint1(BaseModel): + """ + A Value Constraint containing `enum`, `const`, or both. + """ + + model_config = ConfigDict( + extra="allow", + ) + enum: list[Any] = Field(..., min_length=1) + """ + A non-empty array of unique JSON values. + """ + const: Any | None = None + + +class ValueConstraint2(BaseModel): + """ + A Value Constraint containing `enum`, `const`, or both. + """ + + model_config = ConfigDict( + extra="allow", + ) + enum: list[Any] | None = Field(None, min_length=1) + """ + A non-empty array of unique JSON values. + """ + const: Any + + +ValueConstraint = TypeAliasType( + "ValueConstraint", ValueConstraint1 | ValueConstraint2 +) +""" +A Value Constraint containing `enum`, `const`, or both. +""" + + +class ConstraintExpression(BaseModel): + """ + A closed JSON Schema Draft 2020-12 constraint expression with Object and Value Constraint positions. + """ + + model_config = ConfigDict( + extra="forbid", + ) + required: list[str] | None = Field(None, min_length=1) + """ + Property names required by the constrained object. Must be non-empty: an empty array applies no constraint. + """ + properties: dict[str, ConstraintExpression | ValueConstraint] | None = None + """ + Constraints keyed by property name. Must be non-empty: an empty object applies no constraint. + """ + anyOf: list[ConstraintExpression] | None = None + """ + Alternative Object Constraints. The constrained object must satisfy at least one. A branch must be non-empty: an empty branch is satisfied by every object and neutralizes the alternation. + """ + + @field_validator("required", mode="after") + def _enforce_unique_items_required(cls, value): # noqa: N805 + """JSON Schema uniqueItems: reject duplicate entries.""" + if value is None: + return value + seen = [] + for item in value: + if item in seen: + raise ValueError( + "Items must be unique (schema uniqueItems=true)" + ) + seen.append(item) + return value + + +ConstraintExpression.model_rebuild() diff --git a/src/ucp_sdk/models/schemas/shopping/types/context.py b/src/ucp_sdk/models/schemas/common/types/context.py similarity index 72% rename from src/ucp_sdk/models/schemas/shopping/types/context.py rename to src/ucp_sdk/models/schemas/common/types/context.py index 66b4d73..b4bb158 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/context.py +++ b/src/ucp_sdk/models/schemas/common/types/context.py @@ -21,27 +21,34 @@ from pydantic import BaseModel, ConfigDict, field_validator from . import reverse_domain_name +from .locality import Locality -class Context(BaseModel): - """ - Provisional buyer signals for relevance and localization—not authoritative data. Businesses SHOULD use these values when verified inputs (e.g., shipping address) are absent, and MAY ignore or down-rank them if inconsistent with higher-confidence signals (authenticated account, risk detection) or regulatory constraints (export controls). Eligibility and policy enforcement MUST occur at checkout time using binding transaction data. Context SHOULD be non-identifying and can be disclosed progressively—coarse signals early, finer resolution as the session progresses. Higher-resolution data (shipping address, billing address) supersedes context. - """ - +class PaymentItem(BaseModel): model_config = ConfigDict( extra="allow", ) - address_country: str | None = None + handler: reverse_domain_name.ReverseDomainName """ - The country. Recommended to be in 2-letter ISO 3166-1 alpha-2 format, for example "US". For backward compatibility, a 3-letter ISO 3166-1 alpha-3 country code such as "SGP" or a full country name such as "Singapore" can also be used. Optional hint for market context (currency, availability, pricing)—higher-resolution data (e.g., shipping address) supersedes this value. + Handler registry key advertised in the Business profile's `ucp.payment_handlers`. """ - address_region: str | None = None + types: list[str] | None = None """ - The region in which the locality is, and which is in the country. For example, California or another appropriate first-level Administrative division. Optional hint for progressive localization—higher-resolution data (e.g., shipping address) supersedes this value. + Optional preferred instrument types for this handler, in priority order, aligned with the handler's advertised `payment_instrument.type` values (for example `card` or `bank`). Unrecognized values MUST be ignored. """ - postal_code: str | None = None + + +class Context(Locality): """ - The postal code. For example, 94043. Optional hint for regional refinement—higher-resolution data (e.g., shipping address) supersedes this value. + Provisional buyer signals for relevance and localization—not authoritative data. Businesses SHOULD use these values when verified inputs (e.g., shipping address) are absent, and MAY ignore or down-rank them if inconsistent with higher-confidence signals (authenticated account, risk detection) or regulatory constraints (export controls). Eligibility and policy enforcement MUST occur at checkout time using binding transaction data. Context SHOULD be non-identifying and can be disclosed progressively—coarse signals early, finer resolution as the session progresses. Higher-resolution data (shipping address, billing address) supersedes context. + """ + + model_config = ConfigDict( + extra="allow", + ) + location: str | None = None + """ + Stable, opaque identifier for a Location in the Business's namespace. This provisional, non-binding hint is distinct from the Buyer's locality. The operation specification or an active capability/extension defines its effects. A common example in retail shopping is the default home store ID selected and saved by the user when purchasing groceries. """ intent: str | None = None """ @@ -59,6 +66,10 @@ class Context(BaseModel): """ Buyer claims about eligible benefits such as loyalty membership, payment instrument perks, and similar. Recognized claims MAY inform the Business response (e.g., member-only product availability, adjusted pricing in catalog, provisional discounts at cart or checkout). Businesses MUST ignore unrecognized values without error. Values MUST use reverse-domain naming (e.g., 'com.example.loyalty_gold', 'org.school.student') and MUST be non-identifying. """ + payment: list[PaymentItem] | None = None + """ + Buyer-preferred payment handlers in priority order (most preferred first). Each entry names a handler advertised in the Business profile's `ucp.payment_handlers`, optionally narrowed to preferred instrument types. The Business SHOULD use it to preselect or prioritize the handler (and type, when given) and MAY ignore unavailable or ineligible entries; unrecognized values MUST be ignored without error. + """ @field_validator("eligibility", mode="after") def _enforce_unique_items_eligibility(cls, value): # noqa: N805 diff --git a/src/ucp_sdk/models/schemas/shopping/types/context_create_request.py b/src/ucp_sdk/models/schemas/common/types/context_create_request.py similarity index 68% rename from src/ucp_sdk/models/schemas/shopping/types/context_create_request.py rename to src/ucp_sdk/models/schemas/common/types/context_create_request.py index 6c86b1a..6efdcb9 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/context_create_request.py +++ b/src/ucp_sdk/models/schemas/common/types/context_create_request.py @@ -21,27 +21,34 @@ from pydantic import BaseModel, ConfigDict from . import reverse_domain_name_create_request +from .locality_create_request import LocalityCreateRequest -class ContextCreateRequest(BaseModel): - """ - Provisional buyer signals for relevance and localization—not authoritative data. Businesses SHOULD use these values when verified inputs (e.g., shipping address) are absent, and MAY ignore or down-rank them if inconsistent with higher-confidence signals (authenticated account, risk detection) or regulatory constraints (export controls). Eligibility and policy enforcement MUST occur at checkout time using binding transaction data. Context SHOULD be non-identifying and can be disclosed progressively—coarse signals early, finer resolution as the session progresses. Higher-resolution data (shipping address, billing address) supersedes context. - """ - +class PaymentItem(BaseModel): model_config = ConfigDict( extra="allow", ) - address_country: str | None = None + handler: reverse_domain_name_create_request.ReverseDomainNameCreateRequest """ - The country. Recommended to be in 2-letter ISO 3166-1 alpha-2 format, for example "US". For backward compatibility, a 3-letter ISO 3166-1 alpha-3 country code such as "SGP" or a full country name such as "Singapore" can also be used. Optional hint for market context (currency, availability, pricing)—higher-resolution data (e.g., shipping address) supersedes this value. + Handler registry key advertised in the Business profile's `ucp.payment_handlers`. """ - address_region: str | None = None + types: list[str] | None = None """ - The region in which the locality is, and which is in the country. For example, California or another appropriate first-level Administrative division. Optional hint for progressive localization—higher-resolution data (e.g., shipping address) supersedes this value. + Optional preferred instrument types for this handler, in priority order, aligned with the handler's advertised `payment_instrument.type` values (for example `card` or `bank`). Unrecognized values MUST be ignored. """ - postal_code: str | None = None + + +class ContextCreateRequest(LocalityCreateRequest): """ - The postal code. For example, 94043. Optional hint for regional refinement—higher-resolution data (e.g., shipping address) supersedes this value. + Provisional buyer signals for relevance and localization—not authoritative data. Businesses SHOULD use these values when verified inputs (e.g., shipping address) are absent, and MAY ignore or down-rank them if inconsistent with higher-confidence signals (authenticated account, risk detection) or regulatory constraints (export controls). Eligibility and policy enforcement MUST occur at checkout time using binding transaction data. Context SHOULD be non-identifying and can be disclosed progressively—coarse signals early, finer resolution as the session progresses. Higher-resolution data (shipping address, billing address) supersedes context. + """ + + model_config = ConfigDict( + extra="allow", + ) + location: str | None = None + """ + Stable, opaque identifier for a Location in the Business's namespace. This provisional, non-binding hint is distinct from the Buyer's locality. The operation specification or an active capability/extension defines its effects. A common example in retail shopping is the default home store ID selected and saved by the user when purchasing groceries. """ intent: str | None = None """ @@ -62,3 +69,7 @@ class ContextCreateRequest(BaseModel): """ Buyer claims about eligible benefits such as loyalty membership, payment instrument perks, and similar. Recognized claims MAY inform the Business response (e.g., member-only product availability, adjusted pricing in catalog, provisional discounts at cart or checkout). Businesses MUST ignore unrecognized values without error. Values MUST use reverse-domain naming (e.g., 'com.example.loyalty_gold', 'org.school.student') and MUST be non-identifying. """ + payment: list[PaymentItem] | None = None + """ + Buyer-preferred payment handlers in priority order (most preferred first). Each entry names a handler advertised in the Business profile's `ucp.payment_handlers`, optionally narrowed to preferred instrument types. The Business SHOULD use it to preselect or prioritize the handler (and type, when given) and MAY ignore unavailable or ineligible entries; unrecognized values MUST be ignored without error. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/context_update_request.py b/src/ucp_sdk/models/schemas/common/types/context_update_request.py similarity index 68% rename from src/ucp_sdk/models/schemas/shopping/types/context_update_request.py rename to src/ucp_sdk/models/schemas/common/types/context_update_request.py index 79610e9..b0bd4e1 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/context_update_request.py +++ b/src/ucp_sdk/models/schemas/common/types/context_update_request.py @@ -21,27 +21,34 @@ from pydantic import BaseModel, ConfigDict from . import reverse_domain_name_update_request +from .locality_update_request import LocalityUpdateRequest -class ContextUpdateRequest(BaseModel): - """ - Provisional buyer signals for relevance and localization—not authoritative data. Businesses SHOULD use these values when verified inputs (e.g., shipping address) are absent, and MAY ignore or down-rank them if inconsistent with higher-confidence signals (authenticated account, risk detection) or regulatory constraints (export controls). Eligibility and policy enforcement MUST occur at checkout time using binding transaction data. Context SHOULD be non-identifying and can be disclosed progressively—coarse signals early, finer resolution as the session progresses. Higher-resolution data (shipping address, billing address) supersedes context. - """ - +class PaymentItem(BaseModel): model_config = ConfigDict( extra="allow", ) - address_country: str | None = None + handler: reverse_domain_name_update_request.ReverseDomainNameUpdateRequest """ - The country. Recommended to be in 2-letter ISO 3166-1 alpha-2 format, for example "US". For backward compatibility, a 3-letter ISO 3166-1 alpha-3 country code such as "SGP" or a full country name such as "Singapore" can also be used. Optional hint for market context (currency, availability, pricing)—higher-resolution data (e.g., shipping address) supersedes this value. + Handler registry key advertised in the Business profile's `ucp.payment_handlers`. """ - address_region: str | None = None + types: list[str] | None = None """ - The region in which the locality is, and which is in the country. For example, California or another appropriate first-level Administrative division. Optional hint for progressive localization—higher-resolution data (e.g., shipping address) supersedes this value. + Optional preferred instrument types for this handler, in priority order, aligned with the handler's advertised `payment_instrument.type` values (for example `card` or `bank`). Unrecognized values MUST be ignored. """ - postal_code: str | None = None + + +class ContextUpdateRequest(LocalityUpdateRequest): """ - The postal code. For example, 94043. Optional hint for regional refinement—higher-resolution data (e.g., shipping address) supersedes this value. + Provisional buyer signals for relevance and localization—not authoritative data. Businesses SHOULD use these values when verified inputs (e.g., shipping address) are absent, and MAY ignore or down-rank them if inconsistent with higher-confidence signals (authenticated account, risk detection) or regulatory constraints (export controls). Eligibility and policy enforcement MUST occur at checkout time using binding transaction data. Context SHOULD be non-identifying and can be disclosed progressively—coarse signals early, finer resolution as the session progresses. Higher-resolution data (shipping address, billing address) supersedes context. + """ + + model_config = ConfigDict( + extra="allow", + ) + location: str | None = None + """ + Stable, opaque identifier for a Location in the Business's namespace. This provisional, non-binding hint is distinct from the Buyer's locality. The operation specification or an active capability/extension defines its effects. A common example in retail shopping is the default home store ID selected and saved by the user when purchasing groceries. """ intent: str | None = None """ @@ -62,3 +69,7 @@ class ContextUpdateRequest(BaseModel): """ Buyer claims about eligible benefits such as loyalty membership, payment instrument perks, and similar. Recognized claims MAY inform the Business response (e.g., member-only product availability, adjusted pricing in catalog, provisional discounts at cart or checkout). Businesses MUST ignore unrecognized values without error. Values MUST use reverse-domain naming (e.g., 'com.example.loyalty_gold', 'org.school.student') and MUST be non-identifying. """ + payment: list[PaymentItem] | None = None + """ + Buyer-preferred payment handlers in priority order (most preferred first). Each entry names a handler advertised in the Business profile's `ucp.payment_handlers`, optionally narrowed to preferred instrument types. The Business SHOULD use it to preselect or prioritize the handler (and type, when given) and MAY ignore unavailable or ineligible entries; unrecognized values MUST be ignored without error. + """ diff --git a/src/ucp_sdk/models/schemas/common/types/daily_hour.py b/src/ucp_sdk/models/schemas/common/types/daily_hour.py new file mode 100644 index 0000000..15aae94 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/daily_hour.py @@ -0,0 +1,49 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Any, Literal + +from pydantic import ConfigDict + +from .time_interval import TimeInterval + + +class DailyHour(TimeInterval): + """ + A regular weekly operating interval. Its `day`, `opens`, and `closes` are recurring local civil values interpreted in the containing Location's `timezone`. Multiple entries for the same day support split shifts. + """ + + model_config = ConfigDict( + extra="allow", + ) + day: Literal[ + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday", + ] + """ + A stable UCP day-of-week identifier for the day on which this recurring local civil-time interval begins in the containing Location's `timezone`. It is not localized display text. + """ + opens: Any + closes: Any diff --git a/src/ucp_sdk/models/schemas/common/types/daily_hour_create_request.py b/src/ucp_sdk/models/schemas/common/types/daily_hour_create_request.py new file mode 100644 index 0000000..4dc0904 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/daily_hour_create_request.py @@ -0,0 +1,33 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict + +from .time_interval_create_request import TimeIntervalCreateRequest + + +class DailyHourCreateRequest(TimeIntervalCreateRequest): + """ + A regular weekly operating interval. Its `day`, `opens`, and `closes` are recurring local civil values interpreted in the containing Location's `timezone`. Multiple entries for the same day support split shifts. + """ + + model_config = ConfigDict( + extra="allow", + ) diff --git a/src/ucp_sdk/models/schemas/common/types/daily_hour_update_request.py b/src/ucp_sdk/models/schemas/common/types/daily_hour_update_request.py new file mode 100644 index 0000000..08e341f --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/daily_hour_update_request.py @@ -0,0 +1,33 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict + +from .time_interval_update_request import TimeIntervalUpdateRequest + + +class DailyHourUpdateRequest(TimeIntervalUpdateRequest): + """ + A regular weekly operating interval. Its `day`, `opens`, and `closes` are recurring local civil values interpreted in the containing Location's `timezone`. Multiple entries for the same day support split shifts. + """ + + model_config = ConfigDict( + extra="allow", + ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/description.py b/src/ucp_sdk/models/schemas/common/types/description.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/description.py rename to src/ucp_sdk/models/schemas/common/types/description.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/error_code.py b/src/ucp_sdk/models/schemas/common/types/error_code.py similarity index 92% rename from src/ucp_sdk/models/schemas/shopping/types/error_code.py rename to src/ucp_sdk/models/schemas/common/types/error_code.py index 346ac0c..07e34dd 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/error_code.py +++ b/src/ucp_sdk/models/schemas/common/types/error_code.py @@ -44,5 +44,5 @@ ], ) """ -Error code identifying the type of error. Standard errors are defined in specification (see examples), and have standardized semantics; freeform codes are permitted. +Error code identifying the type of error. Standard errors are defined in capability specifications (see examples) and have standardized semantics; freeform codes are permitted. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/error_code_create_request.py b/src/ucp_sdk/models/schemas/common/types/error_code_create_request.py similarity index 92% rename from src/ucp_sdk/models/schemas/shopping/types/error_code_create_request.py rename to src/ucp_sdk/models/schemas/common/types/error_code_create_request.py index c70c8e7..7ccc827 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/error_code_create_request.py +++ b/src/ucp_sdk/models/schemas/common/types/error_code_create_request.py @@ -44,5 +44,5 @@ ], ) """ -Error code identifying the type of error. Standard errors are defined in specification (see examples), and have standardized semantics; freeform codes are permitted. +Error code identifying the type of error. Standard errors are defined in capability specifications (see examples) and have standardized semantics; freeform codes are permitted. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/error_code_update_request.py b/src/ucp_sdk/models/schemas/common/types/error_code_update_request.py similarity index 92% rename from src/ucp_sdk/models/schemas/shopping/types/error_code_update_request.py rename to src/ucp_sdk/models/schemas/common/types/error_code_update_request.py index fab61ba..ec18e56 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/error_code_update_request.py +++ b/src/ucp_sdk/models/schemas/common/types/error_code_update_request.py @@ -44,5 +44,5 @@ ], ) """ -Error code identifying the type of error. Standard errors are defined in specification (see examples), and have standardized semantics; freeform codes are permitted. +Error code identifying the type of error. Standard errors are defined in capability specifications (see examples) and have standardized semantics; freeform codes are permitted. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/error_response.py b/src/ucp_sdk/models/schemas/common/types/error_response.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/error_response.py rename to src/ucp_sdk/models/schemas/common/types/error_response.py diff --git a/src/ucp_sdk/models/schemas/common/types/exception_hour.py b/src/ucp_sdk/models/schemas/common/types/exception_hour.py new file mode 100644 index 0000000..bed674f --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/exception_hour.py @@ -0,0 +1,47 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from datetime import date + +from pydantic import ConfigDict + +from .time_interval import TimeInterval + + +class ExceptionHour(TimeInterval): + """ + A date-specific operating interval or full closure. Its `valid_from`, `valid_through`, `opens`, and `closes` are local civil values interpreted in the containing Location's `timezone`. Date bounds are inclusive. + """ + + model_config = ConfigDict( + extra="allow", + ) + title: str | None = None + """ + A short human-readable heading naming the exception (for example, 'Thanksgiving'). Presentation metadata that does not affect schedule evaluation. + """ + valid_from: date + """ + The first local civil date to which this exception applies, interpreted in the containing Location's `timezone`. + """ + valid_through: date + """ + The last local civil date to which this exception applies, interpreted in the containing Location's `timezone`. + """ diff --git a/src/ucp_sdk/models/schemas/common/types/exception_hour_create_request.py b/src/ucp_sdk/models/schemas/common/types/exception_hour_create_request.py new file mode 100644 index 0000000..85ed698 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/exception_hour_create_request.py @@ -0,0 +1,33 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict + +from .time_interval_create_request import TimeIntervalCreateRequest + + +class ExceptionHourCreateRequest(TimeIntervalCreateRequest): + """ + A date-specific operating interval or full closure. Its `valid_from`, `valid_through`, `opens`, and `closes` are local civil values interpreted in the containing Location's `timezone`. Date bounds are inclusive. + """ + + model_config = ConfigDict( + extra="allow", + ) diff --git a/src/ucp_sdk/models/schemas/common/types/exception_hour_update_request.py b/src/ucp_sdk/models/schemas/common/types/exception_hour_update_request.py new file mode 100644 index 0000000..c2f2e11 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/exception_hour_update_request.py @@ -0,0 +1,33 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict + +from .time_interval_update_request import TimeIntervalUpdateRequest + + +class ExceptionHourUpdateRequest(TimeIntervalUpdateRequest): + """ + A date-specific operating interval or full closure. Its `valid_from`, `valid_through`, `opens`, and `closes` are local civil values interpreted in the containing Location's `timezone`. Date bounds are inclusive. + """ + + model_config = ConfigDict( + extra="allow", + ) diff --git a/src/ucp_sdk/models/schemas/common/types/geo.py b/src/ucp_sdk/models/schemas/common/types/geo.py new file mode 100644 index 0000000..a1feb5b --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/geo.py @@ -0,0 +1,39 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class Geo(BaseModel): + """ + WGS 84 geographic coordinates in decimal degrees. + """ + + model_config = ConfigDict( + extra="allow", + ) + latitude: float = Field(..., ge=-90.0, le=90.0) + """ + WGS 84 latitude in decimal degrees. + """ + longitude: float = Field(..., ge=-180.0, le=180.0) + """ + WGS 84 longitude in decimal degrees. + """ diff --git a/src/ucp_sdk/models/schemas/common/types/geo_create_request.py b/src/ucp_sdk/models/schemas/common/types/geo_create_request.py new file mode 100644 index 0000000..87ba9e8 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/geo_create_request.py @@ -0,0 +1,39 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class GeoCreateRequest(BaseModel): + """ + WGS 84 geographic coordinates in decimal degrees. + """ + + model_config = ConfigDict( + extra="allow", + ) + latitude: float = Field(..., ge=-90.0, le=90.0) + """ + WGS 84 latitude in decimal degrees. + """ + longitude: float = Field(..., ge=-180.0, le=180.0) + """ + WGS 84 longitude in decimal degrees. + """ diff --git a/src/ucp_sdk/models/schemas/common/types/geo_update_request.py b/src/ucp_sdk/models/schemas/common/types/geo_update_request.py new file mode 100644 index 0000000..6aa7dfb --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/geo_update_request.py @@ -0,0 +1,39 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class GeoUpdateRequest(BaseModel): + """ + WGS 84 geographic coordinates in decimal degrees. + """ + + model_config = ConfigDict( + extra="allow", + ) + latitude: float = Field(..., ge=-90.0, le=90.0) + """ + WGS 84 latitude in decimal degrees. + """ + longitude: float = Field(..., ge=-180.0, le=180.0) + """ + WGS 84 longitude in decimal degrees. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/info_code.py b/src/ucp_sdk/models/schemas/common/types/info_code.py similarity index 90% rename from src/ucp_sdk/models/schemas/shopping/types/info_code.py rename to src/ucp_sdk/models/schemas/common/types/info_code.py index a06eb3f..e691c86 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/info_code.py +++ b/src/ucp_sdk/models/schemas/common/types/info_code.py @@ -40,5 +40,5 @@ ], ) """ -Info code identifying the type of informational message. Standard codes are defined in capability specs (see examples), and have standardized semantics; freeform codes are permitted. +Info code identifying the type of informational message. Standard codes are defined in capability specifications (see examples) and have standardized semantics; freeform codes are permitted. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/info_code_create_request.py b/src/ucp_sdk/models/schemas/common/types/info_code_create_request.py similarity index 90% rename from src/ucp_sdk/models/schemas/shopping/types/info_code_create_request.py rename to src/ucp_sdk/models/schemas/common/types/info_code_create_request.py index 5a065eb..ad7cba6 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/info_code_create_request.py +++ b/src/ucp_sdk/models/schemas/common/types/info_code_create_request.py @@ -40,5 +40,5 @@ ], ) """ -Info code identifying the type of informational message. Standard codes are defined in capability specs (see examples), and have standardized semantics; freeform codes are permitted. +Info code identifying the type of informational message. Standard codes are defined in capability specifications (see examples) and have standardized semantics; freeform codes are permitted. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/info_code_update_request.py b/src/ucp_sdk/models/schemas/common/types/info_code_update_request.py similarity index 90% rename from src/ucp_sdk/models/schemas/shopping/types/info_code_update_request.py rename to src/ucp_sdk/models/schemas/common/types/info_code_update_request.py index ff78415..65c287f 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/info_code_update_request.py +++ b/src/ucp_sdk/models/schemas/common/types/info_code_update_request.py @@ -40,5 +40,5 @@ ], ) """ -Info code identifying the type of informational message. Standard codes are defined in capability specs (see examples), and have standardized semantics; freeform codes are permitted. +Info code identifying the type of informational message. Standard codes are defined in capability specifications (see examples) and have standardized semantics; freeform codes are permitted. """ diff --git a/src/ucp_sdk/models/schemas/common/types/instrument_group.py b/src/ucp_sdk/models/schemas/common/types/instrument_group.py new file mode 100644 index 0000000..55bb991 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/instrument_group.py @@ -0,0 +1,43 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class InstrumentGroup(BaseModel): + """ + A constraint within an allowed combination that defines which instrument types can fill this group and how many are permitted. + """ + + model_config = ConfigDict( + extra="allow", + ) + types: list[str] = Field(..., min_length=1) + """ + Instrument types accepted by this group (OR logic). Any listed type qualifies. + """ + min: int | None = Field(0, ge=0) + """ + Minimum number of instruments required from this group. Defaults to 0 (optional). + """ + max: int | None = Field(1, ge=1) + """ + Maximum number of instruments allowed from this group. Defaults to 1. MUST be greater than or equal to `min`. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/link.py b/src/ucp_sdk/models/schemas/common/types/link.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/link.py rename to src/ucp_sdk/models/schemas/common/types/link.py diff --git a/src/ucp_sdk/models/schemas/common/types/locality.py b/src/ucp_sdk/models/schemas/common/types/locality.py new file mode 100644 index 0000000..35ff9b8 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/locality.py @@ -0,0 +1,43 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class Locality(BaseModel): + """ + A coarse geographic location — country, region, and postal code. A lightweight alternative to a full postal address. + """ + + model_config = ConfigDict( + extra="allow", + ) + address_country: str | None = None + """ + The country, as a 2-letter ISO 3166-1 alpha-2 code (e.g. "US"). A 3-letter alpha-3 code or full country name MAY also be used. + """ + address_region: str | None = None + """ + The first-level administrative region within the country (e.g. a state or province such as California). + """ + postal_code: str | None = None + """ + The postal code (e.g. "94043"). + """ diff --git a/src/ucp_sdk/models/schemas/common/types/locality_create_request.py b/src/ucp_sdk/models/schemas/common/types/locality_create_request.py new file mode 100644 index 0000000..2b6c4c6 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/locality_create_request.py @@ -0,0 +1,43 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class LocalityCreateRequest(BaseModel): + """ + A coarse geographic location — country, region, and postal code. A lightweight alternative to a full postal address. + """ + + model_config = ConfigDict( + extra="allow", + ) + address_country: str | None = None + """ + The country, as a 2-letter ISO 3166-1 alpha-2 code (e.g. "US"). A 3-letter alpha-3 code or full country name MAY also be used. + """ + address_region: str | None = None + """ + The first-level administrative region within the country (e.g. a state or province such as California). + """ + postal_code: str | None = None + """ + The postal code (e.g. "94043"). + """ diff --git a/src/ucp_sdk/models/schemas/common/types/locality_update_request.py b/src/ucp_sdk/models/schemas/common/types/locality_update_request.py new file mode 100644 index 0000000..26c74ad --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/locality_update_request.py @@ -0,0 +1,43 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class LocalityUpdateRequest(BaseModel): + """ + A coarse geographic location — country, region, and postal code. A lightweight alternative to a full postal address. + """ + + model_config = ConfigDict( + extra="allow", + ) + address_country: str | None = None + """ + The country, as a 2-letter ISO 3166-1 alpha-2 code (e.g. "US"). A 3-letter alpha-3 code or full country name MAY also be used. + """ + address_region: str | None = None + """ + The first-level administrative region within the country (e.g. a state or province such as California). + """ + postal_code: str | None = None + """ + The postal code (e.g. "94043"). + """ diff --git a/src/ucp_sdk/models/schemas/common/types/location.py b/src/ucp_sdk/models/schemas/common/types/location.py new file mode 100644 index 0000000..abf19c9 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/location.py @@ -0,0 +1,69 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + +from . import amenity_type, daily_hour, exception_hour +from . import geo as geo_1 +from .location_summary import LocationSummary + + +class Amenity(BaseModel): + """ + Buyer-facing presentation metadata for one amenity identifier. The containing map key, not this metadata, defines amenity identity and filter matching. + """ + + model_config = ConfigDict( + extra="allow", + ) + description: str = Field(..., min_length=1) + """ + Short, plain-text, buyer-facing label or phrase for the amenity, suitable for direct use in a compact list (e.g., 'Curbside pickup'). The Business SHOULD localize it for the request when possible. This content does not participate in amenity identity or filter matching. + """ + + +class Location(LocationSummary): + """ + The full, rich representation of a physical business location. Builds on the Location Summary schema with discovery-centric details such as geographic coordinates, operating hours, timezone, and amenities. + """ + + model_config = ConfigDict( + extra="allow", + ) + geo: geo_1.Geo | None = None + """ + Geographic coordinates for the location. + """ + amenities: dict[amenity_type.AmenityType, Amenity] | None = None + """ + Static features, services, or capabilities of the Location, keyed by reverse-domain amenity identifier. Each value provides a buyer-facing description; the key alone defines amenity identity and filter matching. + """ + hours: list[daily_hour.DailyHour] | None = None + """ + Regular weekly operating hours whose day and time values use this Location's canonical local civil-time frame. Multiple entries for the same day support split shifts. An omitted day has no regular interval beginning that day; an interval beginning on the preceding day can carry into it. Omission of the entire `hours` property means the regular schedule is unknown. + """ + exception_hours: list[exception_hour.ExceptionHour] | None = None + """ + Date-specific operating-hour exceptions, including full closures, whose date and time values use this Location's canonical local civil-time frame. + """ + timezone: str | None = None + """ + The Business-owned IANA Time Zone Database identifier (e.g., 'America/New_York') defining this Location's canonical local civil-time frame for all returned schedule day, time, and date fields. The Business does not vary this canonical framing by the requesting Platform's or Buyer's timezone. Required when hours or exception_hours is present. + """ diff --git a/src/ucp_sdk/models/schemas/common/types/location_create_request.py b/src/ucp_sdk/models/schemas/common/types/location_create_request.py new file mode 100644 index 0000000..0e1ef77 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/location_create_request.py @@ -0,0 +1,47 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + +from .location_summary_create_request import LocationSummaryCreateRequest + + +class Amenity(BaseModel): + """ + Buyer-facing presentation metadata for one amenity identifier. The containing map key, not this metadata, defines amenity identity and filter matching. + """ + + model_config = ConfigDict( + extra="allow", + ) + description: str = Field(..., min_length=1) + """ + Short, plain-text, buyer-facing label or phrase for the amenity, suitable for direct use in a compact list (e.g., 'Curbside pickup'). The Business SHOULD localize it for the request when possible. This content does not participate in amenity identity or filter matching. + """ + + +class LocationCreateRequest(LocationSummaryCreateRequest): + """ + The full, rich representation of a physical business location. Builds on the Location Summary schema with discovery-centric details such as geographic coordinates, operating hours, timezone, and amenities. + """ + + model_config = ConfigDict( + extra="allow", + ) diff --git a/src/ucp_sdk/models/schemas/common/types/location_distance.py b/src/ucp_sdk/models/schemas/common/types/location_distance.py new file mode 100644 index 0000000..1ac7f56 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/location_distance.py @@ -0,0 +1,41 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + +from . import geo + + +class LocationDistance(BaseModel): + """ + An explicit-center inclusive-radius predicate. The Business compares the unrounded shortest WGS 84 ellipsoidal geodesic distance in RFC 7035 distance unit (meters) from `center` to the Location's authoritative `geo`; a value less than or equal to `max` matches. Implementations MAY use any algorithm that produces the WGS 84 inverse-geodesic result at sufficient precision such that the match outcome agrees with this unrounded comparison. No context, signals, IP, or `serves` fallback, radius clamping, tolerance, or operand substitution is permitted. + """ + + model_config = ConfigDict( + extra="allow", + ) + center: geo.Geo + """ + Explicit center of the radius. The Platform MUST supply it; the Business MUST NOT derive it from context, signals, an IP address, or `serves`. + """ + max: float = Field(..., ge=0.0) + """ + Inclusive maximum distance in RFC 7035 distance unit (meters). A Business unable to honor the supplied value MUST reject the request rather than clamp it or substitute another radius. + """ diff --git a/src/ucp_sdk/models/schemas/common/types/location_filter.py b/src/ucp_sdk/models/schemas/common/types/location_filter.py new file mode 100644 index 0000000..81260f2 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/location_filter.py @@ -0,0 +1,90 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Annotated + +from pydantic import ( + AwareDatetime, + BaseModel, + ConfigDict, + Field, + field_validator, +) +from typing_extensions import TypeAliasType + +from . import amenity_type + + +class Hours(BaseModel): + """ + Filter by operating hours, evaluated at the one supplied instant. + """ + + model_config = ConfigDict( + extra="allow", + ) + open_at: AwareDatetime = Field( + ..., pattern="(?:[Zz]|[+-](?:[01][0-9]|2[0-3]):[0-5][0-9])$" + ) + """ + The RFC 3339 instant at which matching Locations must be open, expressed with `Z` or a numeric offset. The Platform selects the instant that represents the Buyer's intent. The Business evaluates it exactly as supplied using each Location's authoritative `timezone`; the supplied offset does not identify that timezone. + """ + + +Item = TypeAliasType("Item", Annotated[str, Field(..., min_length=1)]) +""" +A non-empty, opaque Business-scoped item identifier. +""" + + +class LocationFilter(BaseModel): + """ + Filter criteria to narrow Location Search and Lookup results. All supplied filters combine with AND. + """ + + model_config = ConfigDict( + extra="allow", + ) + hours: Hours | None = None + """ + Filter by operating hours, evaluated at the one supplied instant. + """ + amenities: list[amenity_type.AmenityType] | None = None + """ + Filter by amenity identifier. A Location matches only when its `amenities` map contains every supplied identifier as an exact key; descriptions and namespace prefixes do not participate in matching. + """ + items: list[Item] | None = Field(None, min_length=1) + """ + Current item-availability filter. A candidate Location matches only when the Business can currently provide every referenced item at that Location; all references combine with AND. + """ + + @field_validator("items", mode="after") + def _enforce_unique_items_items(cls, value): # noqa: N805 + """JSON Schema uniqueItems: reject duplicate entries.""" + if value is None: + return value + seen = [] + for item in value: + if item in seen: + raise ValueError( + "Items must be unique (schema uniqueItems=true)" + ) + seen.append(item) + return value diff --git a/src/ucp_sdk/models/schemas/common/types/location_serves.py b/src/ucp_sdk/models/schemas/common/types/location_serves.py new file mode 100644 index 0000000..c5c9f4b --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/location_serves.py @@ -0,0 +1,86 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field, model_validator + +from . import geo +from .locality import Locality + + +class Address(Locality): + """ + Coarse locality of the service target. + """ + + model_config = ConfigDict( + extra="allow", + ) + address_country: str = Field(..., min_length=1) + + +class Address1(Locality): + """ + Coarse locality of the service target. + """ + + model_config = ConfigDict( + extra="allow", + ) + address_region: str = Field(..., min_length=1) + + +class Address2(Locality): + """ + Coarse locality of the service target. + """ + + model_config = ConfigDict( + extra="allow", + ) + postal_code: str = Field(..., min_length=1) + + +class LocationServes(BaseModel): + """ + A one-entry map whose key names the authoritative service-target representation. The Platform MUST supply exactly one target form. A Business that cannot evaluate a well-formed target, or receives an extension form that was not negotiated, MUST reject the request rather than ignore it, fall back, or broaden results. This dictionary-like representation map cannot host an ambient `ucp` member. + """ + + model_config = ConfigDict( + extra="allow", + ) + point: geo.Geo | None = None + """ + WGS 84 coordinates of the service target. + """ + address: Address | Address1 | Address2 | None = None + """ + Coarse locality of the service target. + """ + + @model_validator(mode="after") + def _enforce_min_properties(self): + """JSON Schema minProperties: require at least 1 + provided property.""" + provided = self.model_fields_set | set(self.model_extra or {}) + if len(provided) < 1: + raise ValueError( + "At least 1 property must be provided (schema minProperties=1)" + ) + return self diff --git a/src/ucp_sdk/models/schemas/common/types/location_serves_create_request.py b/src/ucp_sdk/models/schemas/common/types/location_serves_create_request.py new file mode 100644 index 0000000..2c1e4fb --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/location_serves_create_request.py @@ -0,0 +1,86 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field, model_validator + +from . import geo_create_request +from .locality_create_request import LocalityCreateRequest + + +class Address(LocalityCreateRequest): + """ + Coarse locality of the service target. + """ + + model_config = ConfigDict( + extra="allow", + ) + address_country: str = Field(..., min_length=1) + + +class Address4(LocalityCreateRequest): + """ + Coarse locality of the service target. + """ + + model_config = ConfigDict( + extra="allow", + ) + address_region: str = Field(..., min_length=1) + + +class Address5(LocalityCreateRequest): + """ + Coarse locality of the service target. + """ + + model_config = ConfigDict( + extra="allow", + ) + postal_code: str = Field(..., min_length=1) + + +class LocationServesCreateRequest(BaseModel): + """ + A one-entry map whose key names the authoritative service-target representation. The Platform MUST supply exactly one target form. A Business that cannot evaluate a well-formed target, or receives an extension form that was not negotiated, MUST reject the request rather than ignore it, fall back, or broaden results. This dictionary-like representation map cannot host an ambient `ucp` member. + """ + + model_config = ConfigDict( + extra="allow", + ) + point: geo_create_request.GeoCreateRequest | None = None + """ + WGS 84 coordinates of the service target. + """ + address: Address | Address4 | Address5 | None = None + """ + Coarse locality of the service target. + """ + + @model_validator(mode="after") + def _enforce_min_properties(self): + """JSON Schema minProperties: require at least 1 + provided property.""" + provided = self.model_fields_set | set(self.model_extra or {}) + if len(provided) < 1: + raise ValueError( + "At least 1 property must be provided (schema minProperties=1)" + ) + return self diff --git a/src/ucp_sdk/models/schemas/common/types/location_serves_update_request.py b/src/ucp_sdk/models/schemas/common/types/location_serves_update_request.py new file mode 100644 index 0000000..99f3dd1 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/location_serves_update_request.py @@ -0,0 +1,86 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field, model_validator + +from . import geo_update_request +from .locality_update_request import LocalityUpdateRequest + + +class Address(LocalityUpdateRequest): + """ + Coarse locality of the service target. + """ + + model_config = ConfigDict( + extra="allow", + ) + address_country: str = Field(..., min_length=1) + + +class Address7(LocalityUpdateRequest): + """ + Coarse locality of the service target. + """ + + model_config = ConfigDict( + extra="allow", + ) + address_region: str = Field(..., min_length=1) + + +class Address8(LocalityUpdateRequest): + """ + Coarse locality of the service target. + """ + + model_config = ConfigDict( + extra="allow", + ) + postal_code: str = Field(..., min_length=1) + + +class LocationServesUpdateRequest(BaseModel): + """ + A one-entry map whose key names the authoritative service-target representation. The Platform MUST supply exactly one target form. A Business that cannot evaluate a well-formed target, or receives an extension form that was not negotiated, MUST reject the request rather than ignore it, fall back, or broaden results. This dictionary-like representation map cannot host an ambient `ucp` member. + """ + + model_config = ConfigDict( + extra="allow", + ) + point: geo_update_request.GeoUpdateRequest | None = None + """ + WGS 84 coordinates of the service target. + """ + address: Address | Address7 | Address8 | None = None + """ + Coarse locality of the service target. + """ + + @model_validator(mode="after") + def _enforce_min_properties(self): + """JSON Schema minProperties: require at least 1 + provided property.""" + provided = self.model_fields_set | set(self.model_extra or {}) + if len(provided) < 1: + raise ValueError( + "At least 1 property must be provided (schema minProperties=1)" + ) + return self diff --git a/src/ucp_sdk/models/schemas/shopping/types/retail_location.py b/src/ucp_sdk/models/schemas/common/types/location_summary.py similarity index 84% rename from src/ucp_sdk/models/schemas/shopping/types/retail_location.py rename to src/ucp_sdk/models/schemas/common/types/location_summary.py index c0353af..52bab23 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/retail_location.py +++ b/src/ucp_sdk/models/schemas/common/types/location_summary.py @@ -23,9 +23,9 @@ from . import postal_address -class RetailLocation(BaseModel): +class LocationSummary(BaseModel): """ - A pickup location (retail store, locker, etc.). + A summary of a physical business location. """ model_config = ConfigDict( @@ -33,11 +33,11 @@ class RetailLocation(BaseModel): ) id: str """ - Unique location identifier. + Stable, opaque, Business-scoped Location identifier. """ name: str """ - Location name (e.g., store name). + Buyer-facing, Business-owned display name. """ address: postal_address.PostalAddress | None = None """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/account_info.py b/src/ucp_sdk/models/schemas/common/types/location_summary_create_request.py similarity index 74% rename from src/ucp_sdk/models/schemas/shopping/types/account_info.py rename to src/ucp_sdk/models/schemas/common/types/location_summary_create_request.py index aeb2665..dbb9a82 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/account_info.py +++ b/src/ucp_sdk/models/schemas/common/types/location_summary_create_request.py @@ -21,15 +21,15 @@ from pydantic import BaseModel, ConfigDict -class PaymentAccountInfo(BaseModel): +class LocationSummaryCreateRequest(BaseModel): """ - Non-sensitive backend identifiers for linking. + A summary of a physical business location. """ model_config = ConfigDict( extra="allow", ) - payment_account_reference: str | None = None + id: str """ - EMVCo PAR. A unique identifier linking a payment card to a specific account, enabling tracking across tokens (Apple Pay, physical card, etc). + Stable, opaque, Business-scoped Location identifier. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/retail_location_create_request.py b/src/ucp_sdk/models/schemas/common/types/location_summary_update_request.py similarity index 70% rename from src/ucp_sdk/models/schemas/shopping/types/retail_location_create_request.py rename to src/ucp_sdk/models/schemas/common/types/location_summary_update_request.py index 4d42caf..7460738 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/retail_location_create_request.py +++ b/src/ucp_sdk/models/schemas/common/types/location_summary_update_request.py @@ -20,24 +20,16 @@ from pydantic import BaseModel, ConfigDict -from . import postal_address_create_request - -class RetailLocationCreateRequest(BaseModel): +class LocationSummaryUpdateRequest(BaseModel): """ - A pickup location (retail store, locker, etc.). + A summary of a physical business location. """ model_config = ConfigDict( extra="allow", ) - name: str - """ - Location name (e.g., store name). - """ - address: postal_address_create_request.PostalAddressCreateRequest | None = ( - None - ) + id: str """ - Physical address of the location. + Stable, opaque, Business-scoped Location identifier. """ diff --git a/src/ucp_sdk/models/schemas/common/types/location_update_request.py b/src/ucp_sdk/models/schemas/common/types/location_update_request.py new file mode 100644 index 0000000..8b5ecca --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/location_update_request.py @@ -0,0 +1,47 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + +from .location_summary_update_request import LocationSummaryUpdateRequest + + +class LocationUpdateRequest(LocationSummaryUpdateRequest): + """ + The full, rich representation of a physical business location. Builds on the Location Summary schema with discovery-centric details such as geographic coordinates, operating hours, timezone, and amenities. + """ + + model_config = ConfigDict( + extra="allow", + ) + + +class Amenity(BaseModel): + """ + Buyer-facing presentation metadata for one amenity identifier. The containing map key, not this metadata, defines amenity identity and filter matching. + """ + + model_config = ConfigDict( + extra="allow", + ) + description: str = Field(..., min_length=1) + """ + Short, plain-text, buyer-facing label or phrase for the amenity, suitable for direct use in a compact list (e.g., 'Curbside pickup'). The Business SHOULD localize it for the request when possible. This content does not participate in amenity identity or filter matching. + """ diff --git a/src/ucp_sdk/models/schemas/common/types/measure.py b/src/ucp_sdk/models/schemas/common/types/measure.py new file mode 100644 index 0000000..f085f51 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/measure.py @@ -0,0 +1,37 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict, Field + +from .unit import Unit + + +class Measure(Unit): + """ + A measure composed of an integer value and a unit descriptor. Its value is the integer count of `10^-scale` units of `unit`. + """ + + model_config = ConfigDict( + extra="allow", + ) + value: int = Field(..., ge=-9007199254740991, le=9007199254740991) + """ + Integer count of `10^-scale` units of `unit`. + """ diff --git a/src/ucp_sdk/models/schemas/common/types/measure_create_request.py b/src/ucp_sdk/models/schemas/common/types/measure_create_request.py new file mode 100644 index 0000000..006326d --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/measure_create_request.py @@ -0,0 +1,37 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict, Field + +from .unit_create_request import UnitCreateRequest + + +class MeasureCreateRequest(UnitCreateRequest): + """ + A measure composed of an integer value and a unit descriptor. Its value is the integer count of `10^-scale` units of `unit`. + """ + + model_config = ConfigDict( + extra="allow", + ) + value: int = Field(..., ge=-9007199254740991, le=9007199254740991) + """ + Integer count of `10^-scale` units of `unit`. + """ diff --git a/src/ucp_sdk/models/schemas/common/types/measure_update_request.py b/src/ucp_sdk/models/schemas/common/types/measure_update_request.py new file mode 100644 index 0000000..b2b08ea --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/measure_update_request.py @@ -0,0 +1,37 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict, Field + +from .unit_update_request import UnitUpdateRequest + + +class MeasureUpdateRequest(UnitUpdateRequest): + """ + A measure composed of an integer value and a unit descriptor. Its value is the integer count of `10^-scale` units of `unit`. + """ + + model_config = ConfigDict( + extra="allow", + ) + value: int = Field(..., ge=-9007199254740991, le=9007199254740991) + """ + Integer count of `10^-scale` units of `unit`. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/media.py b/src/ucp_sdk/models/schemas/common/types/media.py similarity index 96% rename from src/ucp_sdk/models/schemas/shopping/types/media.py rename to src/ucp_sdk/models/schemas/common/types/media.py index dc8338a..b716792 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/media.py +++ b/src/ucp_sdk/models/schemas/common/types/media.py @@ -23,7 +23,7 @@ class Media(BaseModel): """ - Product media item (image, video, etc.). + Media item (image, video, etc.). """ model_config = ConfigDict( diff --git a/src/ucp_sdk/models/schemas/shopping/types/message.py b/src/ucp_sdk/models/schemas/common/types/message.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/message.py rename to src/ucp_sdk/models/schemas/common/types/message.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_create_request.py b/src/ucp_sdk/models/schemas/common/types/message_create_request.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/message_create_request.py rename to src/ucp_sdk/models/schemas/common/types/message_create_request.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_error.py b/src/ucp_sdk/models/schemas/common/types/message_error.py similarity index 71% rename from src/ucp_sdk/models/schemas/shopping/types/message_error.py rename to src/ucp_sdk/models/schemas/common/types/message_error.py index e74117c..9ac973c 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message_error.py +++ b/src/ucp_sdk/models/schemas/common/types/message_error.py @@ -36,7 +36,7 @@ class MessageError(BaseModel): code: error_code.ErrorCode path: str | None = None """ - RFC 9535 JSONPath to the component the message refers to (e.g., $.items[1]). + RFC 9535 JSONPath to the component the message refers to (e.g., $.line_items[0]). """ content_type: Literal["plain", "markdown"] | None = "plain" """ @@ -53,5 +53,5 @@ class MessageError(BaseModel): "unrecoverable", ] """ - Reflects the resource state and recommended action. 'recoverable': platform can resolve by modifying inputs and retrying via API. 'requires_buyer_input': merchant requires information their API doesn't support collecting programmatically (checkout incomplete). 'requires_buyer_review': buyer must authorize before order placement due to policy, regulatory, or entitlement rules. 'unrecoverable': no valid resource exists to act on, retry with new resource or inputs. Errors with 'requires_*' severity contribute to 'status: requires_escalation'. + Reflects the resource state and recommended action. 'recoverable': platform can resolve the condition in band, for example by modifying inputs or processing a related Action, and submit a new operation when needed. 'requires_buyer_input': merchant requires information their API doesn't support collecting programmatically (checkout incomplete). 'requires_buyer_review': buyer must authorize before order placement due to policy, regulatory, or entitlement rules. 'unrecoverable': no valid resource exists to act on, retry with new resource or inputs. Errors with 'requires_*' severity contribute to 'status: requires_escalation'. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_error_create_request.py b/src/ucp_sdk/models/schemas/common/types/message_error_create_request.py similarity index 72% rename from src/ucp_sdk/models/schemas/shopping/types/message_error_create_request.py rename to src/ucp_sdk/models/schemas/common/types/message_error_create_request.py index 9375f6c..cd13b78 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message_error_create_request.py +++ b/src/ucp_sdk/models/schemas/common/types/message_error_create_request.py @@ -36,7 +36,7 @@ class MessageErrorCreateRequest(BaseModel): code: error_code_create_request.ErrorCodeCreateRequest path: str | None = None """ - RFC 9535 JSONPath to the component the message refers to (e.g., $.items[1]). + RFC 9535 JSONPath to the component the message refers to (e.g., $.line_items[0]). """ content_type: Literal["plain", "markdown"] | None = "plain" """ @@ -53,5 +53,5 @@ class MessageErrorCreateRequest(BaseModel): "unrecoverable", ] """ - Reflects the resource state and recommended action. 'recoverable': platform can resolve by modifying inputs and retrying via API. 'requires_buyer_input': merchant requires information their API doesn't support collecting programmatically (checkout incomplete). 'requires_buyer_review': buyer must authorize before order placement due to policy, regulatory, or entitlement rules. 'unrecoverable': no valid resource exists to act on, retry with new resource or inputs. Errors with 'requires_*' severity contribute to 'status: requires_escalation'. + Reflects the resource state and recommended action. 'recoverable': platform can resolve the condition in band, for example by modifying inputs or processing a related Action, and submit a new operation when needed. 'requires_buyer_input': merchant requires information their API doesn't support collecting programmatically (checkout incomplete). 'requires_buyer_review': buyer must authorize before order placement due to policy, regulatory, or entitlement rules. 'unrecoverable': no valid resource exists to act on, retry with new resource or inputs. Errors with 'requires_*' severity contribute to 'status: requires_escalation'. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_error_update_request.py b/src/ucp_sdk/models/schemas/common/types/message_error_update_request.py similarity index 72% rename from src/ucp_sdk/models/schemas/shopping/types/message_error_update_request.py rename to src/ucp_sdk/models/schemas/common/types/message_error_update_request.py index 3ad566d..94fcb77 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message_error_update_request.py +++ b/src/ucp_sdk/models/schemas/common/types/message_error_update_request.py @@ -36,7 +36,7 @@ class MessageErrorUpdateRequest(BaseModel): code: error_code_update_request.ErrorCodeUpdateRequest path: str | None = None """ - RFC 9535 JSONPath to the component the message refers to (e.g., $.items[1]). + RFC 9535 JSONPath to the component the message refers to (e.g., $.line_items[0]). """ content_type: Literal["plain", "markdown"] | None = "plain" """ @@ -53,5 +53,5 @@ class MessageErrorUpdateRequest(BaseModel): "unrecoverable", ] """ - Reflects the resource state and recommended action. 'recoverable': platform can resolve by modifying inputs and retrying via API. 'requires_buyer_input': merchant requires information their API doesn't support collecting programmatically (checkout incomplete). 'requires_buyer_review': buyer must authorize before order placement due to policy, regulatory, or entitlement rules. 'unrecoverable': no valid resource exists to act on, retry with new resource or inputs. Errors with 'requires_*' severity contribute to 'status: requires_escalation'. + Reflects the resource state and recommended action. 'recoverable': platform can resolve the condition in band, for example by modifying inputs or processing a related Action, and submit a new operation when needed. 'requires_buyer_input': merchant requires information their API doesn't support collecting programmatically (checkout incomplete). 'requires_buyer_review': buyer must authorize before order placement due to policy, regulatory, or entitlement rules. 'unrecoverable': no valid resource exists to act on, retry with new resource or inputs. Errors with 'requires_*' severity contribute to 'status: requires_escalation'. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_info.py b/src/ucp_sdk/models/schemas/common/types/message_info.py similarity index 93% rename from src/ucp_sdk/models/schemas/shopping/types/message_info.py rename to src/ucp_sdk/models/schemas/common/types/message_info.py index b8ef016..2057e35 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message_info.py +++ b/src/ucp_sdk/models/schemas/common/types/message_info.py @@ -35,7 +35,7 @@ class MessageInfo(BaseModel): """ path: str | None = None """ - RFC 9535 JSONPath to the component the message refers to. + RFC 9535 JSONPath to the component the message refers to (e.g., $.line_items[0]). """ code: info_code.InfoCode | None = None content_type: Literal["plain", "markdown"] | None = "plain" diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_info_create_request.py b/src/ucp_sdk/models/schemas/common/types/message_info_create_request.py similarity index 93% rename from src/ucp_sdk/models/schemas/shopping/types/message_info_create_request.py rename to src/ucp_sdk/models/schemas/common/types/message_info_create_request.py index aa715d7..79b7738 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message_info_create_request.py +++ b/src/ucp_sdk/models/schemas/common/types/message_info_create_request.py @@ -35,7 +35,7 @@ class MessageInfoCreateRequest(BaseModel): """ path: str | None = None """ - RFC 9535 JSONPath to the component the message refers to. + RFC 9535 JSONPath to the component the message refers to (e.g., $.line_items[0]). """ code: info_code_create_request.InfoCodeCreateRequest | None = None content_type: Literal["plain", "markdown"] | None = "plain" diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_info_update_request.py b/src/ucp_sdk/models/schemas/common/types/message_info_update_request.py similarity index 93% rename from src/ucp_sdk/models/schemas/shopping/types/message_info_update_request.py rename to src/ucp_sdk/models/schemas/common/types/message_info_update_request.py index 5f206ad..6a9f79c 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message_info_update_request.py +++ b/src/ucp_sdk/models/schemas/common/types/message_info_update_request.py @@ -35,7 +35,7 @@ class MessageInfoUpdateRequest(BaseModel): """ path: str | None = None """ - RFC 9535 JSONPath to the component the message refers to. + RFC 9535 JSONPath to the component the message refers to (e.g., $.line_items[0]). """ code: info_code_update_request.InfoCodeUpdateRequest | None = None content_type: Literal["plain", "markdown"] | None = "plain" diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_update_request.py b/src/ucp_sdk/models/schemas/common/types/message_update_request.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/message_update_request.py rename to src/ucp_sdk/models/schemas/common/types/message_update_request.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_warning.py b/src/ucp_sdk/models/schemas/common/types/message_warning.py similarity index 95% rename from src/ucp_sdk/models/schemas/shopping/types/message_warning.py rename to src/ucp_sdk/models/schemas/common/types/message_warning.py index 772134f..e816234 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message_warning.py +++ b/src/ucp_sdk/models/schemas/common/types/message_warning.py @@ -35,7 +35,7 @@ class MessageWarning(BaseModel): """ path: str | None = None """ - JSONPath (RFC 9535) to related field (e.g., $.line_items[0]). + RFC 9535 JSONPath to the component the message refers to (e.g., $.line_items[0]). """ code: warning_code.WarningCode content: str diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_warning_create_request.py b/src/ucp_sdk/models/schemas/common/types/message_warning_create_request.py similarity index 95% rename from src/ucp_sdk/models/schemas/shopping/types/message_warning_create_request.py rename to src/ucp_sdk/models/schemas/common/types/message_warning_create_request.py index 5294a25..712c72f 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message_warning_create_request.py +++ b/src/ucp_sdk/models/schemas/common/types/message_warning_create_request.py @@ -35,7 +35,7 @@ class MessageWarningCreateRequest(BaseModel): """ path: str | None = None """ - JSONPath (RFC 9535) to related field (e.g., $.line_items[0]). + RFC 9535 JSONPath to the component the message refers to (e.g., $.line_items[0]). """ code: warning_code_create_request.WarningCodeCreateRequest content: str diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_warning_update_request.py b/src/ucp_sdk/models/schemas/common/types/message_warning_update_request.py similarity index 95% rename from src/ucp_sdk/models/schemas/shopping/types/message_warning_update_request.py rename to src/ucp_sdk/models/schemas/common/types/message_warning_update_request.py index 44cd08d..83944e6 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message_warning_update_request.py +++ b/src/ucp_sdk/models/schemas/common/types/message_warning_update_request.py @@ -35,7 +35,7 @@ class MessageWarningUpdateRequest(BaseModel): """ path: str | None = None """ - JSONPath (RFC 9535) to related field (e.g., $.line_items[0]). + RFC 9535 JSONPath to the component the message refers to (e.g., $.line_items[0]). """ code: warning_code_update_request.WarningCodeUpdateRequest content: str diff --git a/src/ucp_sdk/models/schemas/common/types/network_token_credential.py b/src/ucp_sdk/models/schemas/common/types/network_token_credential.py new file mode 100644 index 0000000..522e07b --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/network_token_credential.py @@ -0,0 +1,67 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Literal + +from pydantic import ConfigDict, Field + +from .payment_credential import PaymentCredential + + +class NetworkTokenCredential(PaymentCredential): + """ + A card-network token credential verified with a transaction cryptogram. The `number` field carries the network token or wallet-provisioned token rather than the underlying FPAN. + """ + + model_config = ConfigDict( + extra="allow", + ) + type: Literal["network_token"] + """ + The credential type identifier for network token credentials. + """ + number: str = Field(..., examples=["5204240000004242"]) + """ + Network token or wallet-provisioned token replacing the underlying FPAN. + """ + expiry_month: int | None = None + """ + The month of the token's expiration date (1-12). + """ + expiry_year: int | None = None + """ + The year of the token's expiration date. + """ + name: str | None = Field(None, examples=["Jane Doe"]) + """ + Cardholder name. + """ + cryptogram: str = Field(..., examples=["gXc5UCLnM6ckD7pjM1TdPA=="]) + """ + Transaction cryptogram or dynamic CVC (dCVV), in the long or short form expected by the card network or processor. + """ + eci_value: str | None = Field(None, examples=["07"]) + """ + Electronic Commerce Indicator / Security Level Indicator associated with the transaction. + """ + token_requestor_id: str | None = Field(None, examples=["12345678901"]) + """ + Payment network token requestor identifier, when required by the processor or network-token program. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/pagination.py b/src/ucp_sdk/models/schemas/common/types/pagination.py similarity index 84% rename from src/ucp_sdk/models/schemas/shopping/types/pagination.py rename to src/ucp_sdk/models/schemas/common/types/pagination.py index 54aa18d..f33e089 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/pagination.py +++ b/src/ucp_sdk/models/schemas/common/types/pagination.py @@ -43,9 +43,9 @@ class Request(BaseModel): """ Opaque cursor from previous response. """ - limit: int | None = Field(10, ge=1) + limit: int | None = Field(None, ge=1) """ - Requested page size. Implementations MAY clamp to a lower maximum. + Requested page size, not a guaranteed result count. When omitted, the Business MUST apply a default page size. A default of 10 is RECOMMENDED, but the Business MAY choose another value. The Business MAY return fewer results than the requested or default page size, including when enforcing its maximum page size. A Platform MUST NOT assume that the response count equals either value. """ diff --git a/src/ucp_sdk/models/schemas/common/types/pan_credential.py b/src/ucp_sdk/models/schemas/common/types/pan_credential.py new file mode 100644 index 0000000..f4eb86e --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/pan_credential.py @@ -0,0 +1,59 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Literal + +from pydantic import ConfigDict, Field + +from .payment_credential import PaymentCredential + + +class PanCredential(PaymentCredential): + """ + A card credential carrying a funding primary account number (FPAN). Credential selection follows the shape of the value on the wire rather than its provenance: a network token surfaced in PAN form and verified with a `cvc` - as with credentials where a dynamic verification code proxies the cryptogram - is carried here, while a token verified with a discrete `cryptogram` uses Network Token Credential. This credential type MUST NOT be used for checkout, only with payment handlers that tokenize or encrypt credentials. CRITICAL: Both parties handling a PAN credential (sender and receiver) MUST be PCI DSS compliant. Transmission MUST use HTTPS/TLS with strong cipher suites. + """ + + model_config = ConfigDict( + extra="allow", + ) + type: Literal["pan"] + """ + The credential type identifier for PAN credentials. + """ + number: str = Field(..., examples=["4242424242424242"]) + """ + Funding primary account number (FPAN). + """ + expiry_month: int | None = None + """ + The month of the card's expiration date (1-12). + """ + expiry_year: int | None = None + """ + The year of the card's expiration date. + """ + name: str | None = Field(None, examples=["Jane Doe"]) + """ + Cardholder name. + """ + cvc: str | None = Field(None, examples=["223"], max_length=4) + """ + Card verification code. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/payment.py b/src/ucp_sdk/models/schemas/common/types/payment.py similarity index 96% rename from src/ucp_sdk/models/schemas/shopping/payment.py rename to src/ucp_sdk/models/schemas/common/types/payment.py index ba698c7..1f6106a 100644 --- a/src/ucp_sdk/models/schemas/shopping/payment.py +++ b/src/ucp_sdk/models/schemas/common/types/payment.py @@ -20,7 +20,7 @@ from pydantic import BaseModel, ConfigDict -from .types import payment_instrument +from . import payment_instrument class Payment(BaseModel): diff --git a/src/ucp_sdk/models/schemas/shopping/payment_complete_request.py b/src/ucp_sdk/models/schemas/common/types/payment_complete_request.py similarity index 95% rename from src/ucp_sdk/models/schemas/shopping/payment_complete_request.py rename to src/ucp_sdk/models/schemas/common/types/payment_complete_request.py index 392cd20..8955569 100644 --- a/src/ucp_sdk/models/schemas/shopping/payment_complete_request.py +++ b/src/ucp_sdk/models/schemas/common/types/payment_complete_request.py @@ -20,7 +20,7 @@ from pydantic import BaseModel, ConfigDict -from .types import payment_instrument_complete_request +from . import payment_instrument_complete_request class PaymentCompleteRequest(BaseModel): diff --git a/src/ucp_sdk/models/schemas/shopping/payment_create_request.py b/src/ucp_sdk/models/schemas/common/types/payment_create_request.py similarity index 95% rename from src/ucp_sdk/models/schemas/shopping/payment_create_request.py rename to src/ucp_sdk/models/schemas/common/types/payment_create_request.py index 5219f5f..691a23c 100644 --- a/src/ucp_sdk/models/schemas/shopping/payment_create_request.py +++ b/src/ucp_sdk/models/schemas/common/types/payment_create_request.py @@ -20,7 +20,7 @@ from pydantic import BaseModel, ConfigDict -from .types import payment_instrument_create_request +from . import payment_instrument_create_request class PaymentCreateRequest(BaseModel): diff --git a/src/ucp_sdk/models/schemas/shopping/types/payment_credential.py b/src/ucp_sdk/models/schemas/common/types/payment_credential.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/payment_credential.py rename to src/ucp_sdk/models/schemas/common/types/payment_credential.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/payment_credential_complete_request.py b/src/ucp_sdk/models/schemas/common/types/payment_credential_complete_request.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/payment_credential_complete_request.py rename to src/ucp_sdk/models/schemas/common/types/payment_credential_complete_request.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/payment_credential_create_request.py b/src/ucp_sdk/models/schemas/common/types/payment_credential_create_request.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/payment_credential_create_request.py rename to src/ucp_sdk/models/schemas/common/types/payment_credential_create_request.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/payment_credential_update_request.py b/src/ucp_sdk/models/schemas/common/types/payment_credential_update_request.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/payment_credential_update_request.py rename to src/ucp_sdk/models/schemas/common/types/payment_credential_update_request.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/payment_identity.py b/src/ucp_sdk/models/schemas/common/types/payment_identity.py similarity index 93% rename from src/ucp_sdk/models/schemas/shopping/types/payment_identity.py rename to src/ucp_sdk/models/schemas/common/types/payment_identity.py index b30f85d..14b1881 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/payment_identity.py +++ b/src/ucp_sdk/models/schemas/common/types/payment_identity.py @@ -23,7 +23,7 @@ class PaymentIdentity(BaseModel): """ - Identity of a participant for token binding. The access_token uniquely identifies the participant who tokens should be bound to. + Identity of a participant for token binding. The access_token uniquely identifies the participant whom tokens should be issued to. """ model_config = ConfigDict( diff --git a/src/ucp_sdk/models/schemas/shopping/types/payment_instrument.py b/src/ucp_sdk/models/schemas/common/types/payment_instrument.py similarity index 84% rename from src/ucp_sdk/models/schemas/shopping/types/payment_instrument.py rename to src/ucp_sdk/models/schemas/common/types/payment_instrument.py index 4b7bf78..3570af2 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/payment_instrument.py +++ b/src/ucp_sdk/models/schemas/common/types/payment_instrument.py @@ -35,7 +35,7 @@ class PaymentInstrument(BaseModel): ) id: str """ - A unique identifier for this instrument instance, assigned by the platform. + A unique identifier for this instrument instance. Typically assigned by the platform for instruments it collects. For a business-owned saved instrument returned on an identity-linked response, this identifier is assigned by the business; the platform MUST treat it as an opaque, business-scoped reference, and the business resolves it server-side when the buyer selects it. """ handler_id: str """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/payment_instrument_complete_request.py b/src/ucp_sdk/models/schemas/common/types/payment_instrument_complete_request.py similarity index 85% rename from src/ucp_sdk/models/schemas/shopping/types/payment_instrument_complete_request.py rename to src/ucp_sdk/models/schemas/common/types/payment_instrument_complete_request.py index 2aac44e..8109560 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/payment_instrument_complete_request.py +++ b/src/ucp_sdk/models/schemas/common/types/payment_instrument_complete_request.py @@ -38,7 +38,7 @@ class PaymentInstrumentCompleteRequest(BaseModel): ) id: str """ - A unique identifier for this instrument instance, assigned by the platform. + A unique identifier for this instrument instance. Typically assigned by the platform for instruments it collects. For a business-owned saved instrument returned on an identity-linked response, this identifier is assigned by the business; the platform MUST treat it as an opaque, business-scoped reference, and the business resolves it server-side when the buyer selects it. """ handler_id: str """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/payment_instrument_create_request.py b/src/ucp_sdk/models/schemas/common/types/payment_instrument_create_request.py similarity index 85% rename from src/ucp_sdk/models/schemas/shopping/types/payment_instrument_create_request.py rename to src/ucp_sdk/models/schemas/common/types/payment_instrument_create_request.py index 4d7117e..8d000b5 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/payment_instrument_create_request.py +++ b/src/ucp_sdk/models/schemas/common/types/payment_instrument_create_request.py @@ -35,7 +35,7 @@ class PaymentInstrumentCreateRequest(BaseModel): ) id: str """ - A unique identifier for this instrument instance, assigned by the platform. + A unique identifier for this instrument instance. Typically assigned by the platform for instruments it collects. For a business-owned saved instrument returned on an identity-linked response, this identifier is assigned by the business; the platform MUST treat it as an opaque, business-scoped reference, and the business resolves it server-side when the buyer selects it. """ handler_id: str """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/payment_instrument_update_request.py b/src/ucp_sdk/models/schemas/common/types/payment_instrument_update_request.py similarity index 85% rename from src/ucp_sdk/models/schemas/shopping/types/payment_instrument_update_request.py rename to src/ucp_sdk/models/schemas/common/types/payment_instrument_update_request.py index 0a72443..825dbd0 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/payment_instrument_update_request.py +++ b/src/ucp_sdk/models/schemas/common/types/payment_instrument_update_request.py @@ -35,7 +35,7 @@ class PaymentInstrumentUpdateRequest(BaseModel): ) id: str """ - A unique identifier for this instrument instance, assigned by the platform. + A unique identifier for this instrument instance. Typically assigned by the platform for instruments it collects. For a business-owned saved instrument returned on an identity-linked response, this identifier is assigned by the business; the platform MUST treat it as an opaque, business-scoped reference, and the business resolves it server-side when the buyer selects it. """ handler_id: str """ diff --git a/src/ucp_sdk/models/schemas/common/types/payment_schedule.py b/src/ucp_sdk/models/schemas/common/types/payment_schedule.py new file mode 100644 index 0000000..865aade --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/payment_schedule.py @@ -0,0 +1,54 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import AwareDatetime, BaseModel, ConfigDict + +from . import amount as amount_1 +from . import description as description_1 + + +class PaymentSchedule(BaseModel): + """ + A single payment that settles part or all of the checkout under a payment term. Timing is stated in buyer-facing text; `type` and `due_at` are supplementary machine-readable signals derived from it. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + Identifier for this payment schedule, unique within its payment term. Businesses SHOULD keep it stable across responses while the schedule remains the same payment. + """ + type: str + """ + Timing class, drawn from an open vocabulary. `immediate` is the only value with defined meaning: the payment is due when the checkout is completed. Any other value means the payment is not due at completion, and `description` states when it is due. Whether a due payment is authorized, captured, or settled at that moment is payment-handler behavior and outside this extension. Businesses MAY use additional values (e.g. `deferred`, `on_shipment`); Platforms MUST treat unrecognized values as not due at completion. + """ + description: description_1.Description + """ + Complete buyer-facing statement of when and how this payment is due. Businesses MUST make this field sufficient on its own: a Platform that recognizes no `type` value and reads no other field MUST be able to present this schedule correctly. Platforms MAY use `type` and `due_at` for enhanced presentation, but MUST NOT present derived timing that contradicts this field. + """ + due_at: AwareDatetime | None = None + """ + Absolute RFC 3339 date-time when this payment is due, when the Business can determine one at checkout. Supplementary to `description`, never a replacement for it. Omitted when the due date depends on a future event (e.g. 'due on delivery'); the timing is then stated in `description` alone. + """ + amount: amount_1.Amount + """ + The amount charged when this payment is taken, inclusive of tax and every other charge, in the Checkout currency's minor units (ISO 4217). A schedule states an amount rather than a totals breakdown: the purchase is priced once at the Checkout, and a schedule moves part or all of that price. Where the selected term changes what the purchase costs, that difference appears in `checkout.totals`, not here. + """ diff --git a/src/ucp_sdk/models/schemas/common/types/payment_term.py b/src/ucp_sdk/models/schemas/common/types/payment_term.py new file mode 100644 index 0000000..e2a6ee4 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/payment_term.py @@ -0,0 +1,50 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + +from . import description as description_1 +from . import payment_schedule + + +class PaymentTerm(BaseModel): + """ + A way of paying for the checkout: one or more payment schedules that together cover its total. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + Unique identifier for this payment term within the checkout. Referenced by `payment.selected_term_id`. + """ + title: str + """ + Short label that distinguishes this term from its siblings (e.g. 'Pay now', 'Pay in 4', 'Deposit + balance at check-in'). + """ + description: description_1.Description | None = None + """ + Supplementary context for the title (e.g. 'Save 5% by paying today'). Directly renderable; MUST NOT repeat the title. + """ + schedules: list[payment_schedule.PaymentSchedule] = Field(..., min_length=1) + """ + Payment schedules that settle this checkout under this term, in the order they come due. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/payment_update_request.py b/src/ucp_sdk/models/schemas/common/types/payment_update_request.py similarity index 95% rename from src/ucp_sdk/models/schemas/shopping/payment_update_request.py rename to src/ucp_sdk/models/schemas/common/types/payment_update_request.py index 59c926a..e6c3658 100644 --- a/src/ucp_sdk/models/schemas/shopping/payment_update_request.py +++ b/src/ucp_sdk/models/schemas/common/types/payment_update_request.py @@ -20,7 +20,7 @@ from pydantic import BaseModel, ConfigDict -from .types import payment_instrument_update_request +from . import payment_instrument_update_request class PaymentUpdateRequest(BaseModel): diff --git a/src/ucp_sdk/models/schemas/common/types/policy.py b/src/ucp_sdk/models/schemas/common/types/policy.py new file mode 100644 index 0000000..9db2ca7 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/policy.py @@ -0,0 +1,50 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import AnyUrl, BaseModel, ConfigDict + +from . import description as description_1 +from . import reverse_domain_name + + +class Policy(BaseModel): + """ + A durable business rule about the items in a response — return/refund terms, warranty, and the like — at the time of purchase. Every policy carries a `type` (an open reverse-DNS vocabulary) and a `description` so a platform can present it without understanding its type-specific fields; type-specific fields (gated by `type`) add structured context for platforms that model that type. Policies are reference data; the obligation to display a term to the buyer is carried by a `messages[]` warning whose `code` equals the policy `type` — see the Policies section of the specification. + """ + + model_config = ConfigDict( + extra="allow", + ) + type: reverse_domain_name.ReverseDomainName + """ + Policy type discriminator. Open reverse-DNS vocabulary. Well-known values: `dev.ucp.shopping.policy.return` (return terms), `dev.ucp.shopping.policy.warranty` (warranty terms). Businesses MAY define custom types in their own domain (e.g., `com.example.policy.price_match`). Platforms MUST tolerate unknown values. + """ + description: description_1.Description + """ + Human-readable policy summary in one or more formats (plain, markdown, html). Required on every policy so a platform can present it without understanding any type-specific fields. This is not the buyer-facing disclosure — display is compelled by a `messages[]` warning (see the Policies section). + """ + applies_to: list[str] | None = None + """ + RFC 9535 JSONPath expressions identifying the nodes this policy applies to, relative to the embedding response root (e.g., `$.line_items[0]` in cart/checkout, `$.products[2]` in catalog). Each target covers the node it names and everything nested under it, so a target on a product also covers its variants. A singular query (RFC 9535 Section 2.3.5.1; name and index selectors only) names a single node; filters, wildcards, and slices match a set. When omitted, the policy applies to the entire response. When policies of the same `type` contest a node, the narrowest target wins and overrides the rest. See the Policies section for how specificity resolves. + """ + url: AnyUrl | None = None + """ + Optional link to the full policy document. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/postal_address.py b/src/ucp_sdk/models/schemas/common/types/postal_address.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/postal_address.py rename to src/ucp_sdk/models/schemas/common/types/postal_address.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/postal_address_complete_request.py b/src/ucp_sdk/models/schemas/common/types/postal_address_complete_request.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/postal_address_complete_request.py rename to src/ucp_sdk/models/schemas/common/types/postal_address_complete_request.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/postal_address_create_request.py b/src/ucp_sdk/models/schemas/common/types/postal_address_create_request.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/postal_address_create_request.py rename to src/ucp_sdk/models/schemas/common/types/postal_address_create_request.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/postal_address_update_request.py b/src/ucp_sdk/models/schemas/common/types/postal_address_update_request.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/postal_address_update_request.py rename to src/ucp_sdk/models/schemas/common/types/postal_address_update_request.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/price.py b/src/ucp_sdk/models/schemas/common/types/price.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/price.py rename to src/ucp_sdk/models/schemas/common/types/price.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/price_filter.py b/src/ucp_sdk/models/schemas/common/types/price_filter.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/price_filter.py rename to src/ucp_sdk/models/schemas/common/types/price_filter.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/price_range.py b/src/ucp_sdk/models/schemas/common/types/price_range.py similarity index 92% rename from src/ucp_sdk/models/schemas/shopping/types/price_range.py rename to src/ucp_sdk/models/schemas/common/types/price_range.py index d3129db..1c8e47b 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/price_range.py +++ b/src/ucp_sdk/models/schemas/common/types/price_range.py @@ -25,7 +25,7 @@ class PriceRange(BaseModel): """ - A price range representing minimum and maximum values (e.g., across product variants). + A price range representing minimum and maximum values (e.g., a common example in retail shopping is when prices vary across product variants). """ model_config = ConfigDict( diff --git a/src/ucp_sdk/models/schemas/common/types/quantity_unit.py b/src/ucp_sdk/models/schemas/common/types/quantity_unit.py new file mode 100644 index 0000000..44d14dd --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/quantity_unit.py @@ -0,0 +1,37 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict, Field + +from .unit import Unit + + +class QuantityUnit(Unit): + """ + Sale-basis descriptor for quantities: the shared unit descriptor plus the Business's ordering policy. Its unit-descriptor machine identity remains (`unit`, effective `scale`); `display_text` and `increment` are excluded from identity and mismatch comparison. + """ + + model_config = ConfigDict( + extra="allow", + ) + increment: int | None = Field(1, ge=1) + """ + Ordering granularity, denominated in steps: the Business sells this item in integer multiples of `increment` steps. Its effective value is the provided value or 1. Advisory merchandising policy, not a representational bound: Platform-authored quantities SHOULD be integer multiples of the effective increment; the Business MAY accept, revise, or reject an off-increment request with a recoverable business outcome and MUST NOT silently reinterpret it. Business-authored quantities (checkout revisions, fulfillment events, adjustments) are bounded only by `scale`. + """ diff --git a/src/ucp_sdk/models/schemas/common/types/quantity_unit_create_request.py b/src/ucp_sdk/models/schemas/common/types/quantity_unit_create_request.py new file mode 100644 index 0000000..7a38b38 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/quantity_unit_create_request.py @@ -0,0 +1,37 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict, Field + +from .unit_create_request import UnitCreateRequest + + +class QuantityUnitCreateRequest(UnitCreateRequest): + """ + Sale-basis descriptor for quantities: the shared unit descriptor plus the Business's ordering policy. Its unit-descriptor machine identity remains (`unit`, effective `scale`); `display_text` and `increment` are excluded from identity and mismatch comparison. + """ + + model_config = ConfigDict( + extra="allow", + ) + increment: int | None = Field(1, ge=1) + """ + Ordering granularity, denominated in steps: the Business sells this item in integer multiples of `increment` steps. Its effective value is the provided value or 1. Advisory merchandising policy, not a representational bound: Platform-authored quantities SHOULD be integer multiples of the effective increment; the Business MAY accept, revise, or reject an off-increment request with a recoverable business outcome and MUST NOT silently reinterpret it. Business-authored quantities (checkout revisions, fulfillment events, adjustments) are bounded only by `scale`. + """ diff --git a/src/ucp_sdk/models/schemas/common/types/quantity_unit_update_request.py b/src/ucp_sdk/models/schemas/common/types/quantity_unit_update_request.py new file mode 100644 index 0000000..cadcad3 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/quantity_unit_update_request.py @@ -0,0 +1,37 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict, Field + +from .unit_update_request import UnitUpdateRequest + + +class QuantityUnitUpdateRequest(UnitUpdateRequest): + """ + Sale-basis descriptor for quantities: the shared unit descriptor plus the Business's ordering policy. Its unit-descriptor machine identity remains (`unit`, effective `scale`); `display_text` and `increment` are excluded from identity and mismatch comparison. + """ + + model_config = ConfigDict( + extra="allow", + ) + increment: int | None = Field(1, ge=1) + """ + Ordering granularity, denominated in steps: the Business sells this item in integer multiples of `increment` steps. Its effective value is the provided value or 1. Advisory merchandising policy, not a representational bound: Platform-authored quantities SHOULD be integer multiples of the effective increment; the Business MAY accept, revise, or reject an off-increment request with a recoverable business outcome and MUST NOT silently reinterpret it. Business-authored quantities (checkout revisions, fulfillment events, adjustments) are bounded only by `scale`. + """ diff --git a/src/ucp_sdk/models/schemas/common/types/request_constraints.py b/src/ucp_sdk/models/schemas/common/types/request_constraints.py new file mode 100644 index 0000000..58b3294 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/request_constraints.py @@ -0,0 +1,72 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field, field_validator + +from . import constraint_expression + + +class RequestConstraints(BaseModel): + """ + Binds the shared Constraint Expression grammar to data in the next UCP request to the same resource. + """ + + model_config = ConfigDict( + extra="forbid", + ) + path: str | None = None + """ + A complete RFC 9535 JSONPath query evaluated against the next logical UCP request to the same resource. + """ + required: list[str] | None = Field(None, min_length=1) + """ + Property names required by the constrained object. Must be non-empty: an empty array applies no constraint. + """ + properties: ( + dict[ + str, + constraint_expression.ConstraintExpression + | constraint_expression.ValueConstraint, + ] + | None + ) = Field(None, min_length=1) + """ + Constraints keyed by property name. Must be non-empty: an empty object applies no constraint. + """ + anyOf: list[constraint_expression.ConstraintExpression] | None = Field( + None, min_length=1 + ) + """ + Alternative Object Constraints. The constrained object must satisfy at least one. A branch must be non-empty: an empty branch is satisfied by every object and neutralizes the alternation. + """ + + @field_validator("required", mode="after") + def _enforce_unique_items_required(cls, value): # noqa: N805 + """JSON Schema uniqueItems: reject duplicate entries.""" + if value is None: + return value + seen = [] + for item in value: + if item in seen: + raise ValueError( + "Items must be unique (schema uniqueItems=true)" + ) + seen.append(item) + return value diff --git a/src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name.py b/src/ucp_sdk/models/schemas/common/types/reverse_domain_name.py similarity index 57% rename from src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name.py rename to src/ucp_sdk/models/schemas/common/types/reverse_domain_name.py index 3905247..733442e 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name.py +++ b/src/ucp_sdk/models/schemas/common/types/reverse_domain_name.py @@ -29,11 +29,20 @@ str, Field( ..., - pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$", + examples=[ + "dev.ucp.shopping.checkout", + "dev.ucp.common.identity_linking", + "com.example.loyalty_gold", + "com.example-shop.checkout", + "com.2example.cart", + "uk.co.example-shop.checkout", + "xn--p1ai.example.checkout", + ], + pattern="^[a-z](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9_-]*[a-z0-9_])?)+$", title="Reverse Domain Name", ), ], ) """ -Reverse-domain identifier used for collision-safe namespacing of capabilities, services, handlers, eligibility claims, and extension-contributed keys. Must contain at least two dot-separated segments (e.g., 'dev.ucp.shopping.checkout', 'com.example.loyalty_gold'). +Reverse-domain identifier used for collision-safe namespacing of capabilities, services, handlers, eligibility claims, and extension-contributed keys. Must contain at least two dot-separated segments (e.g., 'dev.ucp.shopping.checkout', 'com.example.loyalty_gold'). Segments after the first are domain- or identifier-derived: they may contain interior hyphens, may start with a digit, and may contain underscores (e.g., 'com.example-shop.checkout', 'com.2example.cart', 'dev.ucp.common.identity_linking'), but must not start or end with a hyphen. The first segment (the reversed top-level domain) is letters and digits, and may contain interior hyphens to support internationalized (punycode) top-level domains such as 'xn--p1ai'. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name_create_request.py b/src/ucp_sdk/models/schemas/common/types/reverse_domain_name_create_request.py similarity index 58% rename from src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name_create_request.py rename to src/ucp_sdk/models/schemas/common/types/reverse_domain_name_create_request.py index 01bec46..b8ae8ba 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name_create_request.py +++ b/src/ucp_sdk/models/schemas/common/types/reverse_domain_name_create_request.py @@ -29,11 +29,20 @@ str, Field( ..., - pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$", + examples=[ + "dev.ucp.shopping.checkout", + "dev.ucp.common.identity_linking", + "com.example.loyalty_gold", + "com.example-shop.checkout", + "com.2example.cart", + "uk.co.example-shop.checkout", + "xn--p1ai.example.checkout", + ], + pattern="^[a-z](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9_-]*[a-z0-9_])?)+$", title="Reverse Domain Name Create Request", ), ], ) """ -Reverse-domain identifier used for collision-safe namespacing of capabilities, services, handlers, eligibility claims, and extension-contributed keys. Must contain at least two dot-separated segments (e.g., 'dev.ucp.shopping.checkout', 'com.example.loyalty_gold'). +Reverse-domain identifier used for collision-safe namespacing of capabilities, services, handlers, eligibility claims, and extension-contributed keys. Must contain at least two dot-separated segments (e.g., 'dev.ucp.shopping.checkout', 'com.example.loyalty_gold'). Segments after the first are domain- or identifier-derived: they may contain interior hyphens, may start with a digit, and may contain underscores (e.g., 'com.example-shop.checkout', 'com.2example.cart', 'dev.ucp.common.identity_linking'), but must not start or end with a hyphen. The first segment (the reversed top-level domain) is letters and digits, and may contain interior hyphens to support internationalized (punycode) top-level domains such as 'xn--p1ai'. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name_update_request.py b/src/ucp_sdk/models/schemas/common/types/reverse_domain_name_update_request.py similarity index 58% rename from src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name_update_request.py rename to src/ucp_sdk/models/schemas/common/types/reverse_domain_name_update_request.py index 613ed16..f0beeca 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name_update_request.py +++ b/src/ucp_sdk/models/schemas/common/types/reverse_domain_name_update_request.py @@ -29,11 +29,20 @@ str, Field( ..., - pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$", + examples=[ + "dev.ucp.shopping.checkout", + "dev.ucp.common.identity_linking", + "com.example.loyalty_gold", + "com.example-shop.checkout", + "com.2example.cart", + "uk.co.example-shop.checkout", + "xn--p1ai.example.checkout", + ], + pattern="^[a-z](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9_-]*[a-z0-9_])?)+$", title="Reverse Domain Name Update Request", ), ], ) """ -Reverse-domain identifier used for collision-safe namespacing of capabilities, services, handlers, eligibility claims, and extension-contributed keys. Must contain at least two dot-separated segments (e.g., 'dev.ucp.shopping.checkout', 'com.example.loyalty_gold'). +Reverse-domain identifier used for collision-safe namespacing of capabilities, services, handlers, eligibility claims, and extension-contributed keys. Must contain at least two dot-separated segments (e.g., 'dev.ucp.shopping.checkout', 'com.example.loyalty_gold'). Segments after the first are domain- or identifier-derived: they may contain interior hyphens, may start with a digit, and may contain underscores (e.g., 'com.example-shop.checkout', 'com.2example.cart', 'dev.ucp.common.identity_linking'), but must not start or end with a hyphen. The first segment (the reversed top-level domain) is letters and digits, and may contain interior hyphens to support internationalized (punycode) top-level domains such as 'xn--p1ai'. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/signals.py b/src/ucp_sdk/models/schemas/common/types/signals.py similarity index 95% rename from src/ucp_sdk/models/schemas/shopping/types/signals.py rename to src/ucp_sdk/models/schemas/common/types/signals.py index 2e0ad9c..7176f00 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/signals.py +++ b/src/ucp_sdk/models/schemas/common/types/signals.py @@ -44,7 +44,7 @@ class Signals(BaseModel): def _enforce_property_names(self): """JSON Schema propertyNames: every extra key must match the declared reverse-domain pattern (schema propertyNames).""" - pattern = "^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$" + pattern = "^[a-z](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9_-]*[a-z0-9_])?)+$" for key in self.model_extra or {}: if re.fullmatch(pattern, key) is None: raise ValueError( diff --git a/src/ucp_sdk/models/schemas/shopping/types/signals_complete_request.py b/src/ucp_sdk/models/schemas/common/types/signals_complete_request.py similarity index 95% rename from src/ucp_sdk/models/schemas/shopping/types/signals_complete_request.py rename to src/ucp_sdk/models/schemas/common/types/signals_complete_request.py index fa2ecbf..0e46c33 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/signals_complete_request.py +++ b/src/ucp_sdk/models/schemas/common/types/signals_complete_request.py @@ -44,7 +44,7 @@ class SignalsCompleteRequest(BaseModel): def _enforce_property_names(self): """JSON Schema propertyNames: every extra key must match the declared reverse-domain pattern (schema propertyNames).""" - pattern = "^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$" + pattern = "^[a-z](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9_-]*[a-z0-9_])?)+$" for key in self.model_extra or {}: if re.fullmatch(pattern, key) is None: raise ValueError( diff --git a/src/ucp_sdk/models/schemas/shopping/types/signals_create_request.py b/src/ucp_sdk/models/schemas/common/types/signals_create_request.py similarity index 95% rename from src/ucp_sdk/models/schemas/shopping/types/signals_create_request.py rename to src/ucp_sdk/models/schemas/common/types/signals_create_request.py index df0b091..6857861 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/signals_create_request.py +++ b/src/ucp_sdk/models/schemas/common/types/signals_create_request.py @@ -44,7 +44,7 @@ class SignalsCreateRequest(BaseModel): def _enforce_property_names(self): """JSON Schema propertyNames: every extra key must match the declared reverse-domain pattern (schema propertyNames).""" - pattern = "^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$" + pattern = "^[a-z](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9_-]*[a-z0-9_])?)+$" for key in self.model_extra or {}: if re.fullmatch(pattern, key) is None: raise ValueError( diff --git a/src/ucp_sdk/models/schemas/shopping/types/signals_update_request.py b/src/ucp_sdk/models/schemas/common/types/signals_update_request.py similarity index 95% rename from src/ucp_sdk/models/schemas/shopping/types/signals_update_request.py rename to src/ucp_sdk/models/schemas/common/types/signals_update_request.py index 37d37a8..6d6a714 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/signals_update_request.py +++ b/src/ucp_sdk/models/schemas/common/types/signals_update_request.py @@ -44,7 +44,7 @@ class SignalsUpdateRequest(BaseModel): def _enforce_property_names(self): """JSON Schema propertyNames: every extra key must match the declared reverse-domain pattern (schema propertyNames).""" - pattern = "^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$" + pattern = "^[a-z](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9_-]*[a-z0-9_])?)+$" for key in self.model_extra or {}: if re.fullmatch(pattern, key) is None: raise ValueError( diff --git a/src/ucp_sdk/models/schemas/shopping/types/signed_amount.py b/src/ucp_sdk/models/schemas/common/types/signed_amount.py similarity index 84% rename from src/ucp_sdk/models/schemas/shopping/types/signed_amount.py rename to src/ucp_sdk/models/schemas/common/types/signed_amount.py index 481b1c2..95a93cd 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/signed_amount.py +++ b/src/ucp_sdk/models/schemas/common/types/signed_amount.py @@ -24,7 +24,16 @@ from typing_extensions import TypeAliasType SignedAmount = TypeAliasType( - "SignedAmount", Annotated[int, Field(..., title="Signed Amount")] + "SignedAmount", + Annotated[ + int, + Field( + ..., + ge=-9007199254740991, + le=9007199254740991, + title="Signed Amount", + ), + ], ) """ Monetary amount in the currency's minor unit as defined by ISO 4217. Refer to the currency's exponent to determine minor-to-major ratio (e.g., 2 for USD, 0 for JPY, 3 for KWD). May be negative — the sign is intrinsic to the value (e.g., discounts are negative, charges are positive). diff --git a/src/ucp_sdk/models/schemas/shopping/types/signed_amount_create_request.py b/src/ucp_sdk/models/schemas/common/types/signed_amount_create_request.py similarity index 85% rename from src/ucp_sdk/models/schemas/shopping/types/signed_amount_create_request.py rename to src/ucp_sdk/models/schemas/common/types/signed_amount_create_request.py index 436d0f2..baec495 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/signed_amount_create_request.py +++ b/src/ucp_sdk/models/schemas/common/types/signed_amount_create_request.py @@ -25,7 +25,15 @@ SignedAmountCreateRequest = TypeAliasType( "SignedAmountCreateRequest", - Annotated[int, Field(..., title="Signed Amount Create Request")], + Annotated[ + int, + Field( + ..., + ge=-9007199254740991, + le=9007199254740991, + title="Signed Amount Create Request", + ), + ], ) """ Monetary amount in the currency's minor unit as defined by ISO 4217. Refer to the currency's exponent to determine minor-to-major ratio (e.g., 2 for USD, 0 for JPY, 3 for KWD). May be negative — the sign is intrinsic to the value (e.g., discounts are negative, charges are positive). diff --git a/src/ucp_sdk/models/schemas/shopping/types/signed_amount_update_request.py b/src/ucp_sdk/models/schemas/common/types/signed_amount_update_request.py similarity index 85% rename from src/ucp_sdk/models/schemas/shopping/types/signed_amount_update_request.py rename to src/ucp_sdk/models/schemas/common/types/signed_amount_update_request.py index 985bd10..3e09efe 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/signed_amount_update_request.py +++ b/src/ucp_sdk/models/schemas/common/types/signed_amount_update_request.py @@ -25,7 +25,15 @@ SignedAmountUpdateRequest = TypeAliasType( "SignedAmountUpdateRequest", - Annotated[int, Field(..., title="Signed Amount Update Request")], + Annotated[ + int, + Field( + ..., + ge=-9007199254740991, + le=9007199254740991, + title="Signed Amount Update Request", + ), + ], ) """ Monetary amount in the currency's minor unit as defined by ISO 4217. Refer to the currency's exponent to determine minor-to-major ratio (e.g., 2 for USD, 0 for JPY, 3 for KWD). May be negative — the sign is intrinsic to the value (e.g., discounts are negative, charges are positive). diff --git a/src/ucp_sdk/models/schemas/common/types/time_interval.py b/src/ucp_sdk/models/schemas/common/types/time_interval.py new file mode 100644 index 0000000..1417914 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/time_interval.py @@ -0,0 +1,39 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class TimeInterval(BaseModel): + """ + Reusable opening and closing time fields for a containing schedule schema. Containing schemas determine whether the `opens` and `closes` pair is required; this fragment's standalone `{}` is not an interval. + """ + + model_config = ConfigDict( + extra="allow", + ) + opens: str | None = Field(None, pattern="^([01][0-9]|2[0-3]):[0-5][0-9]$") + """ + Opening time in 24-hour HH:MM format. + """ + closes: str | None = Field(None, pattern="^([01][0-9]|2[0-3]):[0-5][0-9]$") + """ + Closing time in 24-hour HH:MM format. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/retail_location_update_request.py b/src/ucp_sdk/models/schemas/common/types/time_interval_create_request.py similarity index 68% rename from src/ucp_sdk/models/schemas/shopping/types/retail_location_update_request.py rename to src/ucp_sdk/models/schemas/common/types/time_interval_create_request.py index b5aa0de..b6d9a5b 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/retail_location_update_request.py +++ b/src/ucp_sdk/models/schemas/common/types/time_interval_create_request.py @@ -20,24 +20,12 @@ from pydantic import BaseModel, ConfigDict -from . import postal_address_update_request - -class RetailLocationUpdateRequest(BaseModel): +class TimeIntervalCreateRequest(BaseModel): """ - A pickup location (retail store, locker, etc.). + Reusable opening and closing time fields for a containing schedule schema. Containing schemas determine whether the `opens` and `closes` pair is required; this fragment's standalone `{}` is not an interval. """ model_config = ConfigDict( extra="allow", ) - name: str - """ - Location name (e.g., store name). - """ - address: postal_address_update_request.PostalAddressUpdateRequest | None = ( - None - ) - """ - Physical address of the location. - """ diff --git a/src/ucp_sdk/models/schemas/common/types/time_interval_update_request.py b/src/ucp_sdk/models/schemas/common/types/time_interval_update_request.py new file mode 100644 index 0000000..907d22e --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/time_interval_update_request.py @@ -0,0 +1,31 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class TimeIntervalUpdateRequest(BaseModel): + """ + Reusable opening and closing time fields for a containing schedule schema. Containing schemas determine whether the `opens` and `closes` pair is required; this fragment's standalone `{}` is not an interval. + """ + + model_config = ConfigDict( + extra="allow", + ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/token_credential.py b/src/ucp_sdk/models/schemas/common/types/token_credential.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/token_credential.py rename to src/ucp_sdk/models/schemas/common/types/token_credential.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/total.py b/src/ucp_sdk/models/schemas/common/types/total.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/total.py rename to src/ucp_sdk/models/schemas/common/types/total.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/total_create_request.py b/src/ucp_sdk/models/schemas/common/types/total_create_request.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/total_create_request.py rename to src/ucp_sdk/models/schemas/common/types/total_create_request.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/total_update_request.py b/src/ucp_sdk/models/schemas/common/types/total_update_request.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/total_update_request.py rename to src/ucp_sdk/models/schemas/common/types/total_update_request.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/totals.py b/src/ucp_sdk/models/schemas/common/types/totals.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/totals.py rename to src/ucp_sdk/models/schemas/common/types/totals.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/totals_create_request.py b/src/ucp_sdk/models/schemas/common/types/totals_create_request.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/totals_create_request.py rename to src/ucp_sdk/models/schemas/common/types/totals_create_request.py diff --git a/src/ucp_sdk/models/schemas/shopping/types/totals_update_request.py b/src/ucp_sdk/models/schemas/common/types/totals_update_request.py similarity index 100% rename from src/ucp_sdk/models/schemas/shopping/types/totals_update_request.py rename to src/ucp_sdk/models/schemas/common/types/totals_update_request.py diff --git a/src/ucp_sdk/models/schemas/common/types/unit.py b/src/ucp_sdk/models/schemas/common/types/unit.py new file mode 100644 index 0000000..bddaa3b --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/unit.py @@ -0,0 +1,43 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class Unit(BaseModel): + """ + A reusable unit descriptor for quantities and measures. Its unit-descriptor machine identity is (`unit`, effective `scale`), where effective `scale` is the provided `scale` or 0; `display_text` is excluded. + """ + + model_config = ConfigDict( + extra="allow", + ) + unit: str + """ + Stable machine identifier. The Business SHOULD use the exact UN/CEFACT Rec20 Common Code when one accurately identifies the unit. Otherwise, the Business MAY use a custom unit identifier and MUST use it consistently for the same unit. The Platform MUST treat an unrecognized identifier as opaque. + """ + scale: int | None = Field(0, ge=0, le=15) + """ + One step equals `10^-scale` of `unit`. When `unit` is `C62`, `scale`, if present, MUST be 0. The maximum of 15 is derived from the interoperable integer range: at scale 16 a single whole unit (10^16 steps) is no longer representable, so larger scales cannot denominate one unit of their own basis. Businesses needing finer granularity use a smaller unit. + """ + display_text: str + """ + Required printable unit label provided by the Business. The Platform MUST use it when it does not recognize `unit`; for a recognized UN/CEFACT Rec 20 Common Code, the Platform MAY substitute its own localized label. It does not participate in unit identity or mismatch comparison. + """ diff --git a/src/ucp_sdk/models/schemas/common/types/unit_create_request.py b/src/ucp_sdk/models/schemas/common/types/unit_create_request.py new file mode 100644 index 0000000..2fd5282 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/unit_create_request.py @@ -0,0 +1,43 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class UnitCreateRequest(BaseModel): + """ + A reusable unit descriptor for quantities and measures. Its unit-descriptor machine identity is (`unit`, effective `scale`), where effective `scale` is the provided `scale` or 0; `display_text` is excluded. + """ + + model_config = ConfigDict( + extra="allow", + ) + unit: str + """ + Stable machine identifier. The Business SHOULD use the exact UN/CEFACT Rec20 Common Code when one accurately identifies the unit. Otherwise, the Business MAY use a custom unit identifier and MUST use it consistently for the same unit. The Platform MUST treat an unrecognized identifier as opaque. + """ + scale: int | None = Field(0, ge=0, le=15) + """ + One step equals `10^-scale` of `unit`. When `unit` is `C62`, `scale`, if present, MUST be 0. The maximum of 15 is derived from the interoperable integer range: at scale 16 a single whole unit (10^16 steps) is no longer representable, so larger scales cannot denominate one unit of their own basis. Businesses needing finer granularity use a smaller unit. + """ + display_text: str + """ + Required printable unit label provided by the Business. The Platform MUST use it when it does not recognize `unit`; for a recognized UN/CEFACT Rec 20 Common Code, the Platform MAY substitute its own localized label. It does not participate in unit identity or mismatch comparison. + """ diff --git a/src/ucp_sdk/models/schemas/common/types/unit_update_request.py b/src/ucp_sdk/models/schemas/common/types/unit_update_request.py new file mode 100644 index 0000000..a38f7d5 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/types/unit_update_request.py @@ -0,0 +1,43 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class UnitUpdateRequest(BaseModel): + """ + A reusable unit descriptor for quantities and measures. Its unit-descriptor machine identity is (`unit`, effective `scale`), where effective `scale` is the provided `scale` or 0; `display_text` is excluded. + """ + + model_config = ConfigDict( + extra="allow", + ) + unit: str + """ + Stable machine identifier. The Business SHOULD use the exact UN/CEFACT Rec20 Common Code when one accurately identifies the unit. Otherwise, the Business MAY use a custom unit identifier and MUST use it consistently for the same unit. The Platform MUST treat an unrecognized identifier as opaque. + """ + scale: int | None = Field(0, ge=0, le=15) + """ + One step equals `10^-scale` of `unit`. When `unit` is `C62`, `scale`, if present, MUST be 0. The maximum of 15 is derived from the interoperable integer range: at scale 16 a single whole unit (10^16 steps) is no longer representable, so larger scales cannot denominate one unit of their own basis. Businesses needing finer granularity use a smaller unit. + """ + display_text: str + """ + Required printable unit label provided by the Business. The Platform MUST use it when it does not recognize `unit`; for a recognized UN/CEFACT Rec 20 Common Code, the Platform MAY substitute its own localized label. It does not participate in unit identity or mismatch comparison. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/warning_code.py b/src/ucp_sdk/models/schemas/common/types/warning_code.py similarity index 88% rename from src/ucp_sdk/models/schemas/shopping/types/warning_code.py rename to src/ucp_sdk/models/schemas/common/types/warning_code.py index 753a9ca..e14ff58 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/warning_code.py +++ b/src/ucp_sdk/models/schemas/common/types/warning_code.py @@ -33,6 +33,7 @@ "final_sale", "prop65", "fulfillment_changed", + "payment_term_changed", "age_restricted", ], title="Warning Code", @@ -40,5 +41,5 @@ ], ) """ -Warning code identifying the type of warning. Standard codes are defined in capability specs (see examples), and have standardized semantics; freeform codes are permitted. +Warning code identifying the type of warning. Standard codes are defined in capability specifications (see examples) and have standardized semantics; freeform codes are permitted. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/warning_code_create_request.py b/src/ucp_sdk/models/schemas/common/types/warning_code_create_request.py similarity index 88% rename from src/ucp_sdk/models/schemas/shopping/types/warning_code_create_request.py rename to src/ucp_sdk/models/schemas/common/types/warning_code_create_request.py index e6dd78d..27d72d9 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/warning_code_create_request.py +++ b/src/ucp_sdk/models/schemas/common/types/warning_code_create_request.py @@ -33,6 +33,7 @@ "final_sale", "prop65", "fulfillment_changed", + "payment_term_changed", "age_restricted", ], title="Warning Code Create Request", @@ -40,5 +41,5 @@ ], ) """ -Warning code identifying the type of warning. Standard codes are defined in capability specs (see examples), and have standardized semantics; freeform codes are permitted. +Warning code identifying the type of warning. Standard codes are defined in capability specifications (see examples) and have standardized semantics; freeform codes are permitted. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/warning_code_update_request.py b/src/ucp_sdk/models/schemas/common/types/warning_code_update_request.py similarity index 88% rename from src/ucp_sdk/models/schemas/shopping/types/warning_code_update_request.py rename to src/ucp_sdk/models/schemas/common/types/warning_code_update_request.py index 0ef6133..e9cb732 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/warning_code_update_request.py +++ b/src/ucp_sdk/models/schemas/common/types/warning_code_update_request.py @@ -33,6 +33,7 @@ "final_sale", "prop65", "fulfillment_changed", + "payment_term_changed", "age_restricted", ], title="Warning Code Update Request", @@ -40,5 +41,5 @@ ], ) """ -Warning code identifying the type of warning. Standard codes are defined in capability specs (see examples), and have standardized semantics; freeform codes are permitted. +Warning code identifying the type of warning. Standard codes are defined in capability specifications (see examples) and have standardized semantics; freeform codes are permitted. """ diff --git a/src/ucp_sdk/models/schemas/payment_handler.py b/src/ucp_sdk/models/schemas/payment_handler.py index 2428186..bf9ffe4 100644 --- a/src/ucp_sdk/models/schemas/payment_handler.py +++ b/src/ucp_sdk/models/schemas/payment_handler.py @@ -23,7 +23,7 @@ from pydantic import AnyUrl, BaseModel, ConfigDict, Field from typing_extensions import TypeAliasType -from .shopping.types import available_payment_instrument +from .common.types import available_payment_instrument PaymentHandler = TypeAliasType( "PaymentHandler", Annotated[Any, Field(..., title="Payment Handler")] diff --git a/src/ucp_sdk/models/schemas/profile.py b/src/ucp_sdk/models/schemas/profile.py new file mode 100644 index 0000000..500c36d --- /dev/null +++ b/src/ucp_sdk/models/schemas/profile.py @@ -0,0 +1,127 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + +from . import ucp as ucp_1 + + +class JwkPublicKey(BaseModel): + """ + Public JSON Web Key used for HTTP Message Signatures and signed webhook verification. UCP profiles publish public keys only; private key material MUST NOT appear in a profile. Well-known key types: EC (ECDSA P-256, P-384) and OKP (EdDSA Ed25519); OKP keys are RECOMMENDED for signers opting into Web Bot Auth (WBA) interop on HTTP transport. A single profile MAY publish keys of either or both types; consumers select keys by kid. The kty, crv, and alg vocabularies are OPEN: verifiers MUST tolerate key types, curves, and algorithms they do not recognize, selecting keys by kid at verification time. An unsupported key affects only the signature that references it (algorithm_unsupported) and MUST NOT cause whole-profile rejection. Additional public JWK members are permitted; consumers ignore unknown members. + """ + + model_config = ConfigDict( + extra="allow", + ) + kid: str + """ + Key identifier referenced by Signature-Input keyid. For keys used in dual-audience (Web Bot Auth) signatures, the kid MUST be the key's JWK SHA-256 Thumbprint (RFC 7638) so UCP-Agent and Signature-Agent lookups resolve the same key; otherwise the kid MAY be any stable string. + """ + kty: str = Field(..., examples=["EC", "OKP"]) + """ + JWK key type. Well-known values: EC for ECDSA (P-256, P-384); OKP for EdDSA (Ed25519). Open vocabulary; verifiers tolerate unrecognized types and select keys by kid. + """ + crv: str | None = Field(None, examples=["P-256", "P-384", "Ed25519"]) + """ + Curve name. Well-known values: P-256, P-384 (EC); Ed25519 (OKP). Open vocabulary. + """ + x: str | None = None + """ + Public key value, base64url-encoded. For EC, the x coordinate (RFC 7518 §6.2); for OKP, the public key (RFC 8037 §2). + """ + y: str | None = None + """ + EC public key y coordinate, base64url-encoded (RFC 7518 §6.2). Not used by OKP keys. + """ + alg: str | None = Field(None, examples=["ES256", "ES384", "EdDSA"]) + """ + JWA algorithm associated with this public key. Optional; verifiers derive the algorithm from crv when alg is omitted. When present for a well-known curve it MUST match: ES256 with P-256, ES384 with P-384, EdDSA with Ed25519. + """ + use: str | None = None + """ + JWK public key use. UCP examples use sig for signatures. + """ + + +class UcpProfileDocument(BaseModel): + """ + Variant-neutral wrapper schema for UCP profile documents. Use the business_schema definition to validate business profiles and the platform_schema definition to validate platform profiles. + """ + + model_config = ConfigDict( + extra="allow", + ) + ucp: ucp_1.Base + """ + Protocol metadata, capabilities, services, and payment handlers advertised by this party. + """ + keys: list[JwkPublicKey] | None = None + """ + Canonical UCP profile field for publishing signing keys, as a JWK Set per RFC 7517. When a profile publishes signing keys, they MUST appear here; this is where every UCP verifier reads them. Publishing keys[] makes the UCP profile a valid JWK Set that a signer can reuse as its Web Bot Auth key source: a WBA-shape verifier resolving via Signature-Agent type=jwks_uri pointed at this profile reads these keys, and the cimd and directory variants reach them through their own documents. See the Deployment Patterns for WBA Interop section in the overview for hosting patterns. + """ + + +class Base(BaseModel): + """ + Common wrapper for UCP profile documents. + """ + + model_config = ConfigDict( + extra="allow", + ) + ucp: ucp_1.Base + """ + Protocol metadata, capabilities, services, and payment handlers advertised by this party. + """ + keys: list[JwkPublicKey] | None = None + """ + Canonical UCP profile field for publishing signing keys, as a JWK Set per RFC 7517. When a profile publishes signing keys, they MUST appear here; this is where every UCP verifier reads them. Publishing keys[] makes the UCP profile a valid JWK Set that a signer can reuse as its Web Bot Auth key source: a WBA-shape verifier resolving via Signature-Agent type=jwks_uri pointed at this profile reads these keys, and the cimd and directory variants reach them through their own documents. See the Deployment Patterns for WBA Interop section in the overview for hosting patterns. + """ + + +class BusinessSchema(BaseModel): + """ + Profile document hosted by a business at /.well-known/ucp. + """ + + model_config = ConfigDict( + extra="allow", + ) + ucp: ucp_1.BusinessSchema + keys: list[JwkPublicKey] | None = None + """ + Canonical UCP profile field for publishing signing keys, as a JWK Set per RFC 7517. When a profile publishes signing keys, they MUST appear here; this is where every UCP verifier reads them. Publishing keys[] makes the UCP profile a valid JWK Set that a signer can reuse as its Web Bot Auth key source: a WBA-shape verifier resolving via Signature-Agent type=jwks_uri pointed at this profile reads these keys, and the cimd and directory variants reach them through their own documents. See the Deployment Patterns for WBA Interop section in the overview for hosting patterns. + """ + + +class PlatformSchema(BaseModel): + """ + Profile document hosted by a platform and advertised to businesses via UCP-Agent. + """ + + model_config = ConfigDict( + extra="allow", + ) + ucp: ucp_1.PlatformSchema + keys: list[JwkPublicKey] | None = None + """ + Canonical UCP profile field for publishing signing keys, as a JWK Set per RFC 7517. When a profile publishes signing keys, they MUST appear here; this is where every UCP verifier reads them. Publishing keys[] makes the UCP profile a valid JWK Set that a signer can reuse as its Web Bot Auth key source: a WBA-shape verifier resolving via Signature-Agent type=jwks_uri pointed at this profile reads these keys, and the cimd and directory variants reach them through their own documents. See the Deployment Patterns for WBA Interop section in the overview for hosting patterns. + """ diff --git a/src/ucp_sdk/models/schemas/service.py b/src/ucp_sdk/models/schemas/service.py index cf33914..c7638e0 100644 --- a/src/ucp_sdk/models/schemas/service.py +++ b/src/ucp_sdk/models/schemas/service.py @@ -27,7 +27,7 @@ "UcpService", Annotated[Any, Field(..., title="UCP Service")] ) """ -Service binding for a specific transport. Each transport binding is a separate entry in the service array. +Service declaration with one transport binding. Each transport binding is a separate entry in the service array; `version` identifies the service, not the transport. """ @@ -103,7 +103,7 @@ class PlatformSchema(BaseModel): """ -class PlatformSchema7(BaseModel): +class PlatformSchema8(BaseModel): """ Full service declaration for platform-level discovery. All transports require `version`, `spec`, and `transport`. REST, MCP, and embedded additionally require `schema`. """ @@ -141,7 +141,7 @@ class PlatformSchema7(BaseModel): """ -class PlatformSchema8(BaseModel): +class PlatformSchema9(BaseModel): """ Full service declaration for platform-level discovery. All transports require `version`, `spec`, and `transport`. REST, MCP, and embedded additionally require `schema`. """ @@ -179,7 +179,7 @@ class PlatformSchema8(BaseModel): """ -class PlatformSchema9(BaseModel): +class PlatformSchema10(BaseModel): """ Full service declaration for platform-level discovery. All transports require `version`, `spec`, and `transport`. REST, MCP, and embedded additionally require `schema`. """ @@ -217,10 +217,10 @@ class PlatformSchema9(BaseModel): """ -PlatformSchema5 = TypeAliasType( - "PlatformSchema5", +PlatformSchema6 = TypeAliasType( + "PlatformSchema6", Annotated[ - PlatformSchema | PlatformSchema7 | PlatformSchema8 | PlatformSchema9, + PlatformSchema | PlatformSchema8 | PlatformSchema9 | PlatformSchema10, Field(..., title="Service (Platform Schema)"), ], ) @@ -267,7 +267,7 @@ class BusinessSchema(BaseModel): """ -class BusinessSchema4(BaseModel): +class BusinessSchema5(BaseModel): """ Service binding for business/merchant configuration. May override platform endpoints. """ @@ -305,7 +305,7 @@ class BusinessSchema4(BaseModel): """ -class BusinessSchema5(BaseModel): +class BusinessSchema6(BaseModel): """ Service binding for business/merchant configuration. May override platform endpoints. """ @@ -361,7 +361,7 @@ class Config(BaseModel): """ -class BusinessSchema6(BaseModel): +class BusinessSchema7(BaseModel): """ Service binding for business/merchant configuration. May override platform endpoints. """ @@ -399,10 +399,10 @@ class BusinessSchema6(BaseModel): """ -BusinessSchema2 = TypeAliasType( - "BusinessSchema2", +BusinessSchema3 = TypeAliasType( + "BusinessSchema3", Annotated[ - BusinessSchema | BusinessSchema4 | BusinessSchema5 | BusinessSchema6, + BusinessSchema | BusinessSchema5 | BusinessSchema6 | BusinessSchema7, Field(..., title="Service (Business Schema)"), ], ) diff --git a/src/ucp_sdk/models/schemas/shopping/buyer_consent.py b/src/ucp_sdk/models/schemas/shopping/buyer_consent.py index 830a810..dbcb931 100644 --- a/src/ucp_sdk/models/schemas/shopping/buyer_consent.py +++ b/src/ucp_sdk/models/schemas/shopping/buyer_consent.py @@ -18,11 +18,13 @@ from __future__ import annotations -from typing import Annotated, Any +from typing import Annotated, Any, Literal from pydantic import BaseModel, ConfigDict, Field from typing_extensions import TypeAliasType +from ..common.types import link, reverse_domain_name +from .cart import Cart as Cart_1 from .checkout import Checkout as Checkout_1 from .types.buyer import Buyer as Buyer_1 @@ -31,39 +33,79 @@ Annotated[Any, Field(..., title="Buyer Consent Extension")], ) """ -Extends Checkout with buyer consent tracking for privacy compliance via the buyer object. +Extends the buyer object with per-purpose consent. Each purpose is keyed by a reverse-DNS identifier and carries the current `granted` state, the `source` of that state (business default or platform-captured buyer decision), a `description`, optional `links`, and optional `segments` for finer-grained channel, vendor, or program decisions scoped to that purpose. """ -class Consent(BaseModel): +class ConsentSegment(BaseModel): """ - User consent states for data processing + A buyer's consent decision for a specific refinement of a parent purpose (e.g., email marketing under the marketing purpose). Overrides the parent's `granted` value for this scope. Segments do not nest further. """ model_config = ConfigDict( extra="allow", ) - analytics: bool | None = None + granted: bool """ - Consent for analytics and performance tracking. + Whether consent has been granted for this segment. Overrides the parent purpose's `granted` value for this specific scope. """ - preferences: bool | None = None + source: Literal["business", "platform"] """ - Consent for storing user preferences. + Identifies the party that asserted the current `granted` value for this segment. `business` means the value reflects the business's default policy; `platform` means the value reflects an explicit buyer decision captured by the platform. """ - marketing: bool | None = None + description: str """ - Consent for marketing communications. + Human-readable description of what the buyer is consenting to within this segment (e.g., 'Promotional emails and exclusive offers'). """ - sale_of_data: bool | None = None + links: list[link.Link] | None = None """ - Consent for selling data to third parties (CCPA). + Optional segment-specific links (e.g., channel terms or privacy disclosures). """ +class ConsentPurpose(BaseModel): + """ + A buyer's consent decision for a purpose (e.g., marketing, analytics). Carries the current binary state, its source (business default or platform-captured buyer decision), human-readable context, and optional refinements scoping the decision to specific channels, vendors, or programs. + """ + + model_config = ConfigDict( + extra="allow", + ) + granted: bool + """ + Whether consent has been granted for this purpose. The `source` field identifies who asserted this state (business default or platform-captured buyer preference). + """ + source: Literal["business", "platform"] + """ + Identifies the party that asserted the current `granted` value. `business` means the value reflects the business's default policy; `platform` means the value reflects an explicit buyer decision captured by the platform. + """ + description: str + """ + Human-readable description of what the buyer is consenting to (e.g., 'Promotional communications across all channels'). + """ + links: list[link.Link] | None = None + """ + Optional links providing context (e.g., privacy policy, terms). + """ + segments: ( + dict[reverse_domain_name.ReverseDomainName, ConsentSegment] | None + ) = None + """ + Optional refinements scoping this purpose to specific channels, vendors, or programs. Keys are reverse-DNS identifiers. UCP currently defines two well-known segment identifiers under `dev.ucp.consent.marketing`: `dev.ucp.consent.marketing.email`, `dev.ucp.consent.marketing.sms`. Other segments follow vendor or merchant reverse-DNS conventions. + """ + + +Consent = TypeAliasType( + "Consent", dict[reverse_domain_name.ReverseDomainName, ConsentPurpose] +) +""" +Per-purpose consent. Keys are reverse-DNS purpose identifiers. UCP defines four well-known purposes: `dev.ucp.consent.marketing`, `dev.ucp.consent.analytics`, `dev.ucp.consent.preferences`, `dev.ucp.consent.sale_or_sharing`. Vendors and merchants may define additional purposes under their own reverse-DNS namespace. +""" + + class Buyer(Buyer_1): """ - Buyer object extended with consent tracking. + Buyer object extended with per-purpose consent. """ model_config = ConfigDict( @@ -71,13 +113,27 @@ class Buyer(Buyer_1): ) consent: Consent | None = None """ - Consent tracking fields. + Per-purpose consent decisions and business-advertised consent options. + """ + + +class Cart(Cart_1): + """ + Cart extended with buyer consent. + """ + + model_config = ConfigDict( + extra="allow", + ) + buyer: Buyer | None = None + """ + Buyer with consent tracking. """ class Checkout(Checkout_1): """ - Checkout extended with consent tracking via buyer object. + Checkout extended with buyer consent. """ model_config = ConfigDict( diff --git a/src/ucp_sdk/models/schemas/shopping/cart.py b/src/ucp_sdk/models/schemas/shopping/cart.py index 97caa5c..96b14aa 100644 --- a/src/ucp_sdk/models/schemas/shopping/cart.py +++ b/src/ucp_sdk/models/schemas/shopping/cart.py @@ -21,13 +21,15 @@ from pydantic import AnyUrl, AwareDatetime, BaseModel, ConfigDict from .. import ucp as ucp_1 +from ..common.types import actions as actions_1 +from ..common.types import context as context_1 +from ..common.types import link, message, policy +from ..common.types import signals as signals_1 +from ..common.types import totals as totals_1 from .checkout import Checkout as Checkout_1 from .types import attribution as attribution_1 from .types import buyer as buyer_1 -from .types import context as context_1 -from .types import line_item, link, message -from .types import signals as signals_1 -from .types import totals as totals_1 +from .types import line_item class Cart(BaseModel): @@ -65,6 +67,10 @@ class Cart(BaseModel): """ Estimated cost breakdown. May be partial if shipping/tax not yet calculable. """ + actions: actions_1.Actions | None = None + """ + Outstanding extension-defined Actions for this cart. + """ messages: list[message.Message] | None = None """ Validation messages, warnings, or informational notices. @@ -73,6 +79,10 @@ class Cart(BaseModel): """ Optional merchant links (policies, FAQs). """ + policies: list[policy.Policy] | None = None + """ + Policies (e.g., return/refund terms) that apply to the items in this cart. `applies_to` targets are relative to the response root; when absent or empty, refer to the URLs in `links[]`. + """ continue_url: AnyUrl | None = None """ URL for cart handoff and session recovery. Enables sharing and human-in-the-loop flows. diff --git a/src/ucp_sdk/models/schemas/shopping/cart_create_request.py b/src/ucp_sdk/models/schemas/shopping/cart_create_request.py index d7a93f6..42788fa 100644 --- a/src/ucp_sdk/models/schemas/shopping/cart_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/cart_create_request.py @@ -20,13 +20,12 @@ from pydantic import BaseModel, ConfigDict +from ..common.types import context_create_request, signals_create_request from .checkout_create_request import CheckoutCreateRequest from .types import ( attribution_create_request, buyer_create_request, - context_create_request, line_item_create_request, - signals_create_request, ) diff --git a/src/ucp_sdk/models/schemas/shopping/cart_update_request.py b/src/ucp_sdk/models/schemas/shopping/cart_update_request.py index 7c523ac..83c6418 100644 --- a/src/ucp_sdk/models/schemas/shopping/cart_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/cart_update_request.py @@ -20,13 +20,12 @@ from pydantic import BaseModel, ConfigDict +from ..common.types import context_update_request, signals_update_request from .checkout_update_request import CheckoutUpdateRequest from .types import ( attribution_update_request, buyer_update_request, - context_update_request, line_item_update_request, - signals_update_request, ) @@ -38,10 +37,6 @@ class CartUpdateRequest(BaseModel): model_config = ConfigDict( extra="allow", ) - id: str - """ - Unique cart identifier. - """ line_items: list[line_item_update_request.LineItemUpdateRequest] """ Cart line items. Same structure as checkout. Full replacement on update. diff --git a/src/ucp_sdk/models/schemas/shopping/catalog_lookup.py b/src/ucp_sdk/models/schemas/shopping/catalog_lookup.py index fed3d93..a6f02fc 100644 --- a/src/ucp_sdk/models/schemas/shopping/catalog_lookup.py +++ b/src/ucp_sdk/models/schemas/shopping/catalog_lookup.py @@ -21,16 +21,17 @@ from pydantic import BaseModel, ConfigDict, Field from .. import ucp as ucp_1 +from ..common.types import actions as actions_1 +from ..common.types import context as context_1 +from ..common.types import message, policy +from ..common.types import signals as signals_1 from .types import attribution as attribution_1 -from .types import context as context_1 from .types import ( detail_option_value, input_correlation, - message, search_filters, selected_option, ) -from .types import signals as signals_1 from .types.product import Product as Product_1 from .types.variant import Variant @@ -80,13 +81,6 @@ class LookupRequest(BaseModel): attribution: attribution_1.Attribution | None = None -class Product(Product_1): - model_config = ConfigDict( - extra="allow", - ) - variants: list[LookupVariant] | None = None - - class GetProductRequest(BaseModel): """ Request body for single-product retrieval. Supports interactive variant narrowing via selected and preferences. @@ -126,22 +120,11 @@ class Option(BaseModel): ) -class DetailProduct(Product_1): - """ - A product in a get_product response, extended with effective selections and availability signals on option values. - """ - +class Product(Product_1): model_config = ConfigDict( extra="allow", ) - selected: list[selected_option.SelectedOption] | None = None - """ - Effective option selections that anchor the featured variant and availability signals. Required when the product has configurable options; may be empty or omitted for products with no option axes. - """ - options: list[Option] | None = None - """ - Product options with availability signals relative to the effective selections. - """ + variants: list[LookupVariant] | None = None class LookupResponse(BaseModel): @@ -153,10 +136,36 @@ class LookupResponse(BaseModel): """ Products matching the requested identifiers. May contain fewer items if some identifiers not found, or more if identifiers match multiple products. """ + actions: actions_1.Actions | None = None + """ + Outstanding extension-defined Actions for this catalog lookup response. + """ messages: list[message.Message] | None = None """ Errors, warnings, or informational messages about the requested items. """ + policies: list[policy.Policy] | None = None + """ + Policies (e.g., return/refund terms) that apply to the products in this response. `applies_to` targets are relative to the response root; when absent or empty, refer to the URLs in `links[]`. + """ + + +class DetailProduct(Product_1): + """ + A product in a get_product response, extended with effective selections and availability signals on option values. + """ + + model_config = ConfigDict( + extra="allow", + ) + selected: list[selected_option.SelectedOption] | None = None + """ + Effective option selections that anchor the featured variant and availability signals. Required when the product has configurable options; may be empty or omitted for products with no option axes. + """ + options: list[Option] | None = None + """ + Product options with availability signals relative to the effective selections. + """ class GetProductResponse(BaseModel): @@ -168,7 +177,15 @@ class GetProductResponse(BaseModel): """ The requested product with full detail. Singular — this is a single-resource operation. """ + actions: actions_1.Actions | None = None + """ + Outstanding extension-defined Actions for this product response. + """ messages: list[message.Message] | None = None """ Warnings or informational messages about the product (e.g., price recently changed, limited availability). """ + policies: list[policy.Policy] | None = None + """ + Policies (e.g., return/refund terms) that apply to this product. `applies_to` targets are relative to the response root; when absent or empty, refer to the URLs in `links[]`. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/catalog_search.py b/src/ucp_sdk/models/schemas/shopping/catalog_search.py index d405d19..6fef5d6 100644 --- a/src/ucp_sdk/models/schemas/shopping/catalog_search.py +++ b/src/ucp_sdk/models/schemas/shopping/catalog_search.py @@ -21,12 +21,14 @@ from pydantic import BaseModel, ConfigDict from .. import ucp as ucp_1 +from ..common.types import actions as actions_1 +from ..common.types import context as context_1 +from ..common.types import message +from ..common.types import pagination as pagination_1 +from ..common.types import policy +from ..common.types import signals as signals_1 from .types import attribution as attribution_1 -from .types import context as context_1 -from .types import message -from .types import pagination as pagination_1 from .types import product, search_filters -from .types import signals as signals_1 class CatalogSearch(BaseModel): @@ -64,7 +66,15 @@ class SearchResponse(BaseModel): Products matching the search criteria. """ pagination: pagination_1.Response | None = None + actions: actions_1.Actions | None = None + """ + Outstanding extension-defined Actions for this catalog search response. + """ messages: list[message.Message] | None = None """ Errors, warnings, or informational messages about the search results. """ + policies: list[policy.Policy] | None = None + """ + Policies (e.g., return/refund terms) that apply to the products in these search results. `applies_to` targets are relative to the response root; when absent or empty, refer to the URLs in `links[]`. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/checkout.py b/src/ucp_sdk/models/schemas/shopping/checkout.py index 5a4259f..ede35c5 100644 --- a/src/ucp_sdk/models/schemas/shopping/checkout.py +++ b/src/ucp_sdk/models/schemas/shopping/checkout.py @@ -23,13 +23,16 @@ from pydantic import AnyUrl, AwareDatetime, BaseModel, ConfigDict from .. import ucp as ucp_1 -from . import payment as payment_1 +from ..common.types import actions as actions_1 +from ..common.types import context as context_1 +from ..common.types import link, message +from ..common.types import payment as payment_1 +from ..common.types import policy +from ..common.types import signals as signals_1 +from ..common.types import totals as totals_1 from .types import attribution as attribution_1 from .types import buyer as buyer_1 -from .types import context as context_1 -from .types import line_item, link, message, order_confirmation -from .types import signals as signals_1 -from .types import totals as totals_1 +from .types import line_item, order_confirmation class Checkout(BaseModel): @@ -65,7 +68,7 @@ class Checkout(BaseModel): "canceled", ] """ - Checkout state indicating the current phase and required action. See Checkout Status lifecycle documentation for state transition details. + Checkout state indicating the current phase and required processing. See Checkout Status lifecycle documentation for state transition details. """ currency: str """ @@ -75,6 +78,10 @@ class Checkout(BaseModel): """ Different cart totals. """ + actions: actions_1.Actions | None = None + """ + Outstanding extension-defined Actions for this checkout. + """ messages: list[message.Message] | None = None """ List of messages with error and info about the checkout session state. @@ -83,6 +90,10 @@ class Checkout(BaseModel): """ Links to be displayed by the platform (Privacy Policy, TOS). Mandatory for legal compliance. """ + policies: list[policy.Policy] | None = None + """ + Policies (e.g., return/refund terms) that apply to the items in this checkout. `applies_to` targets are relative to the response root; when absent or empty, refer to the URLs in `links[]`. + """ expires_at: AwareDatetime | None = None """ RFC 3339 expiry timestamp. Default TTL is 6 hours from creation if not sent. diff --git a/src/ucp_sdk/models/schemas/shopping/checkout_complete_request.py b/src/ucp_sdk/models/schemas/shopping/checkout_complete_request.py index 31a56c6..94068b2 100644 --- a/src/ucp_sdk/models/schemas/shopping/checkout_complete_request.py +++ b/src/ucp_sdk/models/schemas/shopping/checkout_complete_request.py @@ -20,8 +20,8 @@ from pydantic import BaseModel, ConfigDict -from . import payment_complete_request -from .types import attribution_complete_request, signals_complete_request +from ..common.types import payment_complete_request, signals_complete_request +from .types import attribution_complete_request class CheckoutCompleteRequest(BaseModel): diff --git a/src/ucp_sdk/models/schemas/shopping/checkout_create_request.py b/src/ucp_sdk/models/schemas/shopping/checkout_create_request.py index eaea128..d09b1ae 100644 --- a/src/ucp_sdk/models/schemas/shopping/checkout_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/checkout_create_request.py @@ -20,13 +20,15 @@ from pydantic import BaseModel, ConfigDict -from . import payment_create_request +from ..common.types import ( + context_create_request, + payment_create_request, + signals_create_request, +) from .types import ( attribution_create_request, buyer_create_request, - context_create_request, line_item_create_request, - signals_create_request, ) diff --git a/src/ucp_sdk/models/schemas/shopping/checkout_update_request.py b/src/ucp_sdk/models/schemas/shopping/checkout_update_request.py index 045042d..f2eb2c2 100644 --- a/src/ucp_sdk/models/schemas/shopping/checkout_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/checkout_update_request.py @@ -20,13 +20,15 @@ from pydantic import BaseModel, ConfigDict -from . import payment_update_request +from ..common.types import ( + context_update_request, + payment_update_request, + signals_update_request, +) from .types import ( attribution_update_request, buyer_update_request, - context_update_request, line_item_update_request, - signals_update_request, ) diff --git a/src/ucp_sdk/models/schemas/shopping/discount.py b/src/ucp_sdk/models/schemas/shopping/discount.py index f0121ee..33ecbdd 100644 --- a/src/ucp_sdk/models/schemas/shopping/discount.py +++ b/src/ucp_sdk/models/schemas/shopping/discount.py @@ -23,10 +23,10 @@ from pydantic import BaseModel, ConfigDict, Field from typing_extensions import TypeAliasType +from ..common.types import amount as amount_1 +from ..common.types import reverse_domain_name from .cart import Cart as Cart_1 from .checkout import Checkout as Checkout_1 -from .types import amount as amount_1 -from .types import reverse_domain_name DiscountExtension = TypeAliasType( "DiscountExtension", Annotated[Any, Field(..., title="Discount Extension")] @@ -46,7 +46,7 @@ class Allocation(BaseModel): ) path: str """ - JSONPath to the allocation target (e.g., '$.line_items[0]', '$.totals.shipping'). + RFC 9535 JSONPath to the allocation target (e.g., '$.line_items[0]', '$.totals[?@.type == "fulfillment"]'). """ amount: amount_1.Amount """ diff --git a/src/ucp_sdk/models/schemas/shopping/fulfillment.py b/src/ucp_sdk/models/schemas/shopping/fulfillment.py index fd919f5..07238e5 100644 --- a/src/ucp_sdk/models/schemas/shopping/fulfillment.py +++ b/src/ucp_sdk/models/schemas/shopping/fulfillment.py @@ -20,24 +20,39 @@ from typing import Annotated, Any -from pydantic import ConfigDict, Field +from pydantic import BaseModel, ConfigDict, Field from typing_extensions import TypeAliasType +from ..common.types import description as description_1 +from .catalog_lookup import DetailProduct +from .catalog_lookup import GetProductRequest as GetProductRequest_1 +from .catalog_lookup import GetProductResponse as GetProductResponse_1 +from .catalog_lookup import LookupRequest as LookupRequest_1 +from .catalog_lookup import LookupResponse as LookupResponse_1 +from .catalog_lookup import LookupVariant +from .catalog_search import SearchRequest as SearchRequest_1 +from .catalog_search import SearchResponse as SearchResponse_1 from .checkout import Checkout as Checkout_1 +from .types import availability as availability_1 from .types import fulfillment as fulfillment_1 from .types import ( fulfillment_available_method, + fulfillment_destination_filter, fulfillment_group, fulfillment_method, fulfillment_option, + fulfillment_option_base, ) +from .types.product import Product +from .types.search_filters import SearchFilters +from .types.variant import Variant FulfillmentExtension = TypeAliasType( "FulfillmentExtension", Annotated[Any, Field(..., title="Fulfillment Extension")], ) """ -Extends Checkout with fulfillment support using methods, destinations, and groups. +Extends Catalog with fulfillment discovery and Checkout with hierarchical fulfillment. """ @@ -50,11 +65,129 @@ ) +class CatalogFulfillmentMethod(BaseModel): + """ + A fulfillment method on a catalog variant: how the variant can be fulfilled, and its availability. + """ + + model_config = ConfigDict( + extra="allow", + ) + type: str + """ + Fulfillment method type. Well-known values: `shipping`, `pickup`. Businesses MAY use additional values. + """ + description: description_1.Description | None = None + """ + Short buyer-facing summary (e.g. 'Ships in 2–4 business days'). + """ + availability: availability_1.Availability | None = None + """ + Availability of this variant via this method at the specified or inferred location. + """ + location: str | None = None + """ + Stable, opaque identifier for the Business Location resolved for this place-based fulfillment method. The Business recognizes the same ID when submitted as `selected_destination_id` for that method; recognition does not reserve inventory or guarantee eligibility, and current terms are revalidated. + """ + options: list[fulfillment_option_base.FulfillmentOptionBase] | None = None + """ + Representative fulfillment options for this method (e.g. Standard, Express). Without a destination or full cart, a Business SHOULD preview meaningful boundary options (e.g. cheapest, fastest); more specific options are negotiated in Checkout once line items and destination are known. + """ + + +class CatalogFulfillment(BaseModel): + """ + How a catalog variant can be fulfilled. Mirrors checkout `fulfillment`. + """ + + model_config = ConfigDict( + extra="allow", + ) + methods: list[CatalogFulfillmentMethod] | None = None + """ + Fulfillment methods for this variant. + """ + + FulfillmentOption = TypeAliasType( "FulfillmentOption", fulfillment_option.FulfillmentOption ) +class FulfillmentVariant(Variant): + """ + A catalog variant with fulfillment. + """ + + model_config = ConfigDict( + extra="allow", + ) + fulfillment: CatalogFulfillment | None = None + + +class FulfillmentLookupVariant(LookupVariant): + """ + A lookup variant (carrying input correlation) enriched with fulfillment. + """ + + model_config = ConfigDict( + extra="allow", + ) + fulfillment: CatalogFulfillment | None = None + + +class FulfillmentSearchFilters(SearchFilters): + """ + Catalog filters extended with a fulfillment destination filter and a method-type filter. + """ + + model_config = ConfigDict( + extra="allow", + ) + fulfills_to: ( + fulfillment_destination_filter.FulfillmentDestinationFilter | None + ) = None + """ + Explicit destination where items are fulfilled. It may differ from the locality or Business Location supplied in `context` (e.g. a gift delivered directly to the recipient). The filter restricts results to what can be fulfilled there and seeds method `availability`. It supersedes `context` only for fulfillment destination and availability resolution. + """ + methods: list[str] | None = None + """ + Restrict results to these fulfillment method types (e.g. ["pickup"]). Well-known values: `shipping`, `pickup`. + """ + + +class FulfillmentSearchRequest(SearchRequest_1): + model_config = ConfigDict( + extra="allow", + ) + filters: FulfillmentSearchFilters | None = None + + +class FulfillmentLookupRequest(LookupRequest_1): + model_config = ConfigDict( + extra="allow", + ) + filters: FulfillmentSearchFilters | None = None + + +class FulfillmentGetProductRequest(GetProductRequest_1): + model_config = ConfigDict( + extra="allow", + ) + filters: FulfillmentSearchFilters | None = None + + +SearchRequest = TypeAliasType("SearchRequest", FulfillmentSearchRequest) + + +LookupRequest = TypeAliasType("LookupRequest", FulfillmentLookupRequest) + + +GetProductRequest = TypeAliasType( + "GetProductRequest", FulfillmentGetProductRequest +) + + FulfillmentGroup = TypeAliasType( "FulfillmentGroup", fulfillment_group.FulfillmentGroup ) @@ -65,6 +198,71 @@ ) +class FulfillmentProduct(Product): + """ + A catalog product whose variants are fulfillment-enriched. Used by search. + """ + + model_config = ConfigDict( + extra="allow", + ) + variants: list[FulfillmentVariant] | None = None + + +class FulfillmentLookupProduct(Product): + """ + A lookup product whose variants are fulfillment-enriched, preserving input correlation. Used by lookup. + """ + + model_config = ConfigDict( + extra="allow", + ) + variants: list[FulfillmentLookupVariant] | None = None + + +class FulfillmentDetailProduct(DetailProduct): + """ + A get_product detail product (carrying selected/options availability signals) whose variants are fulfillment-enriched. Used by get_product. + """ + + model_config = ConfigDict( + extra="allow", + ) + variants: list[FulfillmentVariant] | None = None + + +class FulfillmentSearchResponse(SearchResponse_1): + model_config = ConfigDict( + extra="allow", + ) + products: list[FulfillmentProduct] | None = None + + +class FulfillmentLookupResponse(LookupResponse_1): + model_config = ConfigDict( + extra="allow", + ) + products: list[FulfillmentLookupProduct] | None = None + + +class FulfillmentGetProductResponse(GetProductResponse_1): + model_config = ConfigDict( + extra="allow", + ) + product: FulfillmentDetailProduct | None = None + + +SearchResponse = TypeAliasType("SearchResponse", FulfillmentSearchResponse) + + +LookupResponse = TypeAliasType("LookupResponse", FulfillmentLookupResponse) + + +GetProductResponse = TypeAliasType( + "GetProductResponse", FulfillmentGetProductResponse +) + + Fulfillment = TypeAliasType("Fulfillment", fulfillment_1.Fulfillment) diff --git a/src/ucp_sdk/models/schemas/shopping/order.py b/src/ucp_sdk/models/schemas/shopping/order.py index ede4135..47e95f0 100644 --- a/src/ucp_sdk/models/schemas/shopping/order.py +++ b/src/ucp_sdk/models/schemas/shopping/order.py @@ -21,10 +21,11 @@ from pydantic import AnyUrl, BaseModel, ConfigDict from .. import ucp as ucp_1 +from ..common.types import message, policy +from ..common.types import totals as totals_1 from .types import adjustment from .types import attribution as attribution_1 -from .types import expectation, fulfillment_event, message, order_line_item -from .types import totals as totals_1 +from .types import expectation, fulfillment_event, order_line_item class PlatformSchema(BaseModel): @@ -104,6 +105,10 @@ class Order(BaseModel): """ Different totals for the order. """ + policies: list[policy.Policy] | None = None + """ + Snapshot of the policies that applied to the items at checkout, captured on the order as a durable record. `applies_to` targets are relative to the response root. + """ messages: list[message.Message] | None = None """ Business outcome messages (errors, warnings, informational). Present when the business needs to communicate status or issues to the platform. diff --git a/src/ucp_sdk/models/schemas/shopping/order_create_request.py b/src/ucp_sdk/models/schemas/shopping/order_create_request.py index a31c315..e5f36ca 100644 --- a/src/ucp_sdk/models/schemas/shopping/order_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/order_create_request.py @@ -21,13 +21,12 @@ from pydantic import AnyUrl, BaseModel, ConfigDict from .. import ucp_create_request +from ..common.types import message_create_request, totals_create_request from .types import ( adjustment_create_request, expectation_create_request, fulfillment_event_create_request, - message_create_request, order_line_item_create_request, - totals_create_request, ) diff --git a/src/ucp_sdk/models/schemas/shopping/order_update_request.py b/src/ucp_sdk/models/schemas/shopping/order_update_request.py index fd4a285..959b3ae 100644 --- a/src/ucp_sdk/models/schemas/shopping/order_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/order_update_request.py @@ -21,13 +21,12 @@ from pydantic import AnyUrl, BaseModel, ConfigDict from .. import ucp_update_request +from ..common.types import message_update_request, totals_update_request from .types import ( adjustment_update_request, expectation_update_request, fulfillment_event_update_request, - message_update_request, order_line_item_update_request, - totals_update_request, ) diff --git a/src/ucp_sdk/models/schemas/shopping/permalink.py b/src/ucp_sdk/models/schemas/shopping/permalink.py new file mode 100644 index 0000000..b05c9a2 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/permalink.py @@ -0,0 +1,52 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Annotated, Any + +from pydantic import AnyUrl, BaseModel, ConfigDict, Field +from typing_extensions import TypeAliasType + +PermalinkCapability = TypeAliasType( + "PermalinkCapability", + Annotated[Any, Field(..., title="Permalink Capability")], +) +""" +Browser-addressable shopping intent capability: defines a Business browser endpoint and redirect resolution. It defines no shopping-state fields of its own; permalink query parameters address existing UCP field paths. +""" + + +Endpoint = TypeAliasType("Endpoint", AnyUrl) +""" +Absolute HTTPS browser endpoint with a non-empty authority and without userinfo, query, fragment, whitespace, backslashes, or trailing slash. Optional compact item path and query parameters are appended to this endpoint. +""" + + +class Config(BaseModel): + """ + Business browser endpoint configuration for shopping permalinks. + """ + + model_config = ConfigDict( + extra="allow", + ) + endpoint: Endpoint + + +Permalink = TypeAliasType("Permalink", Any) diff --git a/src/ucp_sdk/models/schemas/shopping/types/adjustment.py b/src/ucp_sdk/models/schemas/shopping/types/adjustment.py index 99cdfe4..3ae0b00 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/adjustment.py +++ b/src/ucp_sdk/models/schemas/shopping/types/adjustment.py @@ -20,9 +20,10 @@ from typing import Literal -from pydantic import AwareDatetime, BaseModel, ConfigDict +from pydantic import AwareDatetime, BaseModel, ConfigDict, Field -from . import total +from ...common.types import measure as measure_1 +from ...common.types import total class LineItem(BaseModel): @@ -33,9 +34,13 @@ class LineItem(BaseModel): """ Line item ID reference. """ - quantity: int + quantity: int = Field(..., ge=-9007199254740991, le=9007199254740991) """ - Signed quantity affected by this adjustment. Negative values represent reductions (e.g. returns); positive values represent additions (e.g. exchanges). + Signed integer count of steps of the referenced line item's `quantity_unit` (`10^-scale` × `unit`); when `quantity_unit` is absent, it counts whole items (`each`). Negative values represent reductions (e.g. returns); positive values represent additions (e.g. exchanges). + """ + measure: measure_1.Measure | None = None + """ + The settled measurement this adjustment reconciles (for example, actual picked weight), present when the line's price settles by measurement. Its unit identity MUST match the line's pricing basis (`item.unit_price` measure/reference unit); no unit conversion. A pure price settlement uses `quantity: 0` together with `measure` and a totals delta. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/adjustment_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/adjustment_create_request.py index 44881cd..f3a7abb 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/adjustment_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/adjustment_create_request.py @@ -20,9 +20,9 @@ from typing import Literal -from pydantic import AwareDatetime, BaseModel, ConfigDict +from pydantic import AwareDatetime, BaseModel, ConfigDict, Field -from . import total_create_request +from ...common.types import measure_create_request, total_create_request class LineItem(BaseModel): @@ -33,9 +33,13 @@ class LineItem(BaseModel): """ Line item ID reference. """ - quantity: int + quantity: int = Field(..., ge=-9007199254740991, le=9007199254740991) """ - Signed quantity affected by this adjustment. Negative values represent reductions (e.g. returns); positive values represent additions (e.g. exchanges). + Signed integer count of steps of the referenced line item's `quantity_unit` (`10^-scale` × `unit`); when `quantity_unit` is absent, it counts whole items (`each`). Negative values represent reductions (e.g. returns); positive values represent additions (e.g. exchanges). + """ + measure: measure_create_request.MeasureCreateRequest | None = None + """ + The settled measurement this adjustment reconciles (for example, actual picked weight), present when the line's price settles by measurement. Its unit identity MUST match the line's pricing basis (`item.unit_price` measure/reference unit); no unit conversion. A pure price settlement uses `quantity: 0` together with `measure` and a totals delta. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/adjustment_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/adjustment_update_request.py index 632c2e7..3fd1b3b 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/adjustment_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/adjustment_update_request.py @@ -20,9 +20,9 @@ from typing import Literal -from pydantic import AwareDatetime, BaseModel, ConfigDict +from pydantic import AwareDatetime, BaseModel, ConfigDict, Field -from . import total_update_request +from ...common.types import measure_update_request, total_update_request class LineItem(BaseModel): @@ -33,9 +33,13 @@ class LineItem(BaseModel): """ Line item ID reference. """ - quantity: int + quantity: int = Field(..., ge=-9007199254740991, le=9007199254740991) """ - Signed quantity affected by this adjustment. Negative values represent reductions (e.g. returns); positive values represent additions (e.g. exchanges). + Signed integer count of steps of the referenced line item's `quantity_unit` (`10^-scale` × `unit`); when `quantity_unit` is absent, it counts whole items (`each`). Negative values represent reductions (e.g. returns); positive values represent additions (e.g. exchanges). + """ + measure: measure_update_request.MeasureUpdateRequest | None = None + """ + The settled measurement this adjustment reconciles (for example, actual picked weight), present when the line's price settles by measurement. Its unit identity MUST match the line's pricing basis (`item.unit_price` measure/reference unit); no unit conversion. A pure price settlement uses `quantity: 0` together with `measure` and a totals delta. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/availability.py b/src/ucp_sdk/models/schemas/shopping/types/availability.py new file mode 100644 index 0000000..46b9ab9 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/availability.py @@ -0,0 +1,39 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class Availability(BaseModel): + """ + Availability of an item: whether it can be obtained, and a qualifying status. + """ + + model_config = ConfigDict( + extra="allow", + ) + available: bool | None = None + """ + Whether this can be obtained. See status for fulfillment details. + """ + status: str | None = None + """ + Qualifies available with fulfillment state. Well-known values: `in_stock`, `backorder`, `preorder`, `out_of_stock`, `discontinued`. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/business_fulfillment_config.py b/src/ucp_sdk/models/schemas/shopping/types/business_fulfillment_config.py index 55a9007..7515f7e 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/business_fulfillment_config.py +++ b/src/ucp_sdk/models/schemas/shopping/types/business_fulfillment_config.py @@ -18,26 +18,16 @@ from __future__ import annotations -from typing import Literal - from pydantic import BaseModel, ConfigDict -class AllowsMultiDestination(BaseModel): - """ - Permits multiple destinations per method type. - """ - +class MultiDestinationItem(BaseModel): model_config = ConfigDict( - extra="forbid", + extra="allow", ) - shipping: bool | None = None - """ - Multiple shipping destinations allowed. - """ - pickup: bool | None = None + method: str """ - Multiple pickup locations allowed. + Fulfillment method type (e.g. `shipping`, `pickup`). Optional per-method constraints MAY be added alongside. """ @@ -49,13 +39,11 @@ class BusinessFulfillmentConfig(BaseModel): model_config = ConfigDict( extra="allow", ) - allows_multi_destination: AllowsMultiDestination | None = None + multi_destination: list[MultiDestinationItem] | None = None """ - Permits multiple destinations per method type. + Method types that permit multiple destinations within one cart (e.g. split shipping across addresses). Listing a method permits it; an omitted method does not. Open — businesses MAY list any method type. """ - allows_method_combinations: ( - list[list[Literal["shipping", "pickup"]]] | None - ) = None + method_combinations: list[list[str]] | None = None """ - Allowed method type combinations. + Method-type combinations the business permits within one cart. Each inner array is a permitted set of method `type` values (e.g. shipping + pickup). """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/expectation.py b/src/ucp_sdk/models/schemas/shopping/types/expectation.py index 03e8182..59163e2 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/expectation.py +++ b/src/ucp_sdk/models/schemas/shopping/types/expectation.py @@ -18,11 +18,9 @@ from __future__ import annotations -from typing import Literal - from pydantic import BaseModel, ConfigDict, Field -from . import postal_address +from ...common.types import postal_address class LineItem(BaseModel): @@ -33,9 +31,9 @@ class LineItem(BaseModel): """ Line item ID reference. """ - quantity: int = Field(..., ge=1) + quantity: int = Field(..., ge=1, le=9007199254740991) """ - Quantity of this item in this expectation. + Integer count of steps of the referenced line item's `quantity_unit` (`10^-scale` × `unit`); when `quantity_unit` is absent, it counts whole items (`each`). """ @@ -55,9 +53,9 @@ class Expectation(BaseModel): """ Which line items and quantities are in this expectation. """ - method_type: Literal["shipping", "pickup", "digital"] + method_type: str """ - Delivery method type (shipping, pickup, digital). + Delivery method type. Well-known values: `shipping`, `pickup`, `digital`; additional values MAY be used. """ destination: postal_address.PostalAddress """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/expectation_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/expectation_create_request.py index c1084bf..55f491f 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/expectation_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/expectation_create_request.py @@ -18,11 +18,9 @@ from __future__ import annotations -from typing import Literal - from pydantic import BaseModel, ConfigDict, Field -from . import postal_address_create_request +from ...common.types import postal_address_create_request class LineItem(BaseModel): @@ -33,9 +31,9 @@ class LineItem(BaseModel): """ Line item ID reference. """ - quantity: int = Field(..., ge=1) + quantity: int = Field(..., ge=1, le=9007199254740991) """ - Quantity of this item in this expectation. + Integer count of steps of the referenced line item's `quantity_unit` (`10^-scale` × `unit`); when `quantity_unit` is absent, it counts whole items (`each`). """ @@ -55,9 +53,9 @@ class ExpectationCreateRequest(BaseModel): """ Which line items and quantities are in this expectation. """ - method_type: Literal["shipping", "pickup", "digital"] + method_type: str """ - Delivery method type (shipping, pickup, digital). + Delivery method type. Well-known values: `shipping`, `pickup`, `digital`; additional values MAY be used. """ destination: postal_address_create_request.PostalAddressCreateRequest """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/expectation_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/expectation_update_request.py index f0780a9..a49a2d8 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/expectation_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/expectation_update_request.py @@ -18,11 +18,9 @@ from __future__ import annotations -from typing import Literal - from pydantic import BaseModel, ConfigDict, Field -from . import postal_address_update_request +from ...common.types import postal_address_update_request class LineItem(BaseModel): @@ -33,9 +31,9 @@ class LineItem(BaseModel): """ Line item ID reference. """ - quantity: int = Field(..., ge=1) + quantity: int = Field(..., ge=1, le=9007199254740991) """ - Quantity of this item in this expectation. + Integer count of steps of the referenced line item's `quantity_unit` (`10^-scale` × `unit`); when `quantity_unit` is absent, it counts whole items (`each`). """ @@ -55,9 +53,9 @@ class ExpectationUpdateRequest(BaseModel): """ Which line items and quantities are in this expectation. """ - method_type: Literal["shipping", "pickup", "digital"] + method_type: str """ - Delivery method type (shipping, pickup, digital). + Delivery method type. Well-known values: `shipping`, `pickup`, `digital`; additional values MAY be used. """ destination: postal_address_update_request.PostalAddressUpdateRequest """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method.py index 4c9a1bb..d5df375 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method.py @@ -18,8 +18,6 @@ from __future__ import annotations -from typing import Literal - from pydantic import BaseModel, ConfigDict @@ -31,9 +29,9 @@ class FulfillmentAvailableMethod(BaseModel): model_config = ConfigDict( extra="allow", ) - type: Literal["shipping", "pickup"] + type: str """ - Fulfillment method type this availability applies to. + Fulfillment method type this availability applies to. Well-known values: `shipping`, `pickup`; businesses MAY use additional values. """ line_item_ids: list[str] """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination.py index b26f3e3..fa07edf 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination.py @@ -18,21 +18,22 @@ from __future__ import annotations -from typing import Annotated +from pydantic import BaseModel, ConfigDict -from pydantic import Field -from typing_extensions import TypeAliasType -from . import retail_location, shipping_destination +class FulfillmentDestination(BaseModel): + """ + A destination for fulfillment. + """ -FulfillmentDestination = TypeAliasType( - "FulfillmentDestination", - Annotated[ - shipping_destination.ShippingDestination - | retail_location.RetailLocation, - Field(..., title="Fulfillment Destination"), - ], -) -""" -A destination for fulfillment. -""" + model_config = ConfigDict( + extra="allow", + ) + type: str + """ + Destination contract discriminator. Required in Business responses and optional in Platform requests. Well-known values: `shipping_address`, `business_location`. The enclosing method contract defines request defaults and which fields the Platform may write; negotiated extensions define additional values. + """ + id: str + """ + Fulfillment destination identifier. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_create_request.py index 4d7bbb5..d46a2e8 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_create_request.py @@ -18,24 +18,22 @@ from __future__ import annotations -from typing import Annotated +from pydantic import BaseModel, ConfigDict -from pydantic import Field -from typing_extensions import TypeAliasType -from . import ( - retail_location_create_request, - shipping_destination_create_request, -) +class FulfillmentDestinationCreateRequest(BaseModel): + """ + A destination for fulfillment. + """ -FulfillmentDestinationCreateRequest = TypeAliasType( - "FulfillmentDestinationCreateRequest", - Annotated[ - shipping_destination_create_request.ShippingDestinationCreateRequest - | retail_location_create_request.RetailLocationCreateRequest, - Field(..., title="Fulfillment Destination Create Request"), - ], -) -""" -A destination for fulfillment. -""" + model_config = ConfigDict( + extra="allow", + ) + type: str | None = None + """ + Destination contract discriminator. Required in Business responses and optional in Platform requests. Well-known values: `shipping_address`, `business_location`. The enclosing method contract defines request defaults and which fields the Platform may write; negotiated extensions define additional values. + """ + id: str | None = None + """ + Fulfillment destination identifier. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_filter.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_filter.py new file mode 100644 index 0000000..b2af2b1 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_filter.py @@ -0,0 +1,37 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict + +from ...common.types.locality import Locality + + +class FulfillmentDestinationFilter(Locality): + """ + A specific destination, named by value or by reference: a coarse locality (`address_country` / `address_region` / `postal_code`), or a `location` id. Platforms SHOULD provide one or the other, not both; if both are present, a business SHOULD use the more specific — typically `location`. + """ + + model_config = ConfigDict( + extra="allow", + ) + location: str | None = None + """ + A reference to the destination (e.g. store, pickup location, saved address). + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_update_request.py index add2079..0ea2cd1 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_update_request.py @@ -18,24 +18,22 @@ from __future__ import annotations -from typing import Annotated +from pydantic import BaseModel, ConfigDict -from pydantic import Field -from typing_extensions import TypeAliasType -from . import ( - retail_location_update_request, - shipping_destination_update_request, -) +class FulfillmentDestinationUpdateRequest(BaseModel): + """ + A destination for fulfillment. + """ -FulfillmentDestinationUpdateRequest = TypeAliasType( - "FulfillmentDestinationUpdateRequest", - Annotated[ - shipping_destination_update_request.ShippingDestinationUpdateRequest - | retail_location_update_request.RetailLocationUpdateRequest, - Field(..., title="Fulfillment Destination Update Request"), - ], -) -""" -A destination for fulfillment. -""" + model_config = ConfigDict( + extra="allow", + ) + type: str | None = None + """ + Destination contract discriminator. Required in Business responses and optional in Platform requests. Well-known values: `shipping_address`, `business_location`. The enclosing method contract defines request defaults and which fields the Platform may write; negotiated extensions define additional values. + """ + id: str | None = None + """ + Fulfillment destination identifier. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_event.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_event.py index 68e09c6..7ae5f99 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_event.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_event.py @@ -29,9 +29,9 @@ class LineItem(BaseModel): """ Line item ID reference. """ - quantity: int = Field(..., ge=1) + quantity: int = Field(..., ge=1, le=9007199254740991) """ - Quantity fulfilled in this event. + Integer count of steps of the referenced line item's `quantity_unit` (`10^-scale` × `unit`); when `quantity_unit` is absent, it counts whole items (`each`). """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_event_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_event_create_request.py index a40632c..633d362 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_event_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_event_create_request.py @@ -29,9 +29,9 @@ class LineItem(BaseModel): """ Line item ID reference. """ - quantity: int = Field(..., ge=1) + quantity: int = Field(..., ge=1, le=9007199254740991) """ - Quantity fulfilled in this event. + Integer count of steps of the referenced line item's `quantity_unit` (`10^-scale` × `unit`); when `quantity_unit` is absent, it counts whole items (`each`). """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_event_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_event_update_request.py index 2462a71..a67550e 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_event_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_event_update_request.py @@ -29,9 +29,9 @@ class LineItem(BaseModel): """ Line item ID reference. """ - quantity: int = Field(..., ge=1) + quantity: int = Field(..., ge=1, le=9007199254740991) """ - Quantity fulfilled in this event. + Integer count of steps of the referenced line item's `quantity_unit` (`10^-scale` × `unit`); when `quantity_unit` is absent, it counts whole items (`each`). """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method.py index e022d8d..b9f7c06 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method.py @@ -18,8 +18,6 @@ from __future__ import annotations -from typing import Literal - from pydantic import BaseModel, ConfigDict from . import fulfillment_destination, fulfillment_group @@ -27,7 +25,7 @@ class FulfillmentMethod(BaseModel): """ - A fulfillment method (shipping or pickup) with destinations and groups. + A fulfillment method with destinations and groups. """ model_config = ConfigDict( @@ -37,9 +35,9 @@ class FulfillmentMethod(BaseModel): """ Unique fulfillment method identifier. """ - type: Literal["shipping", "pickup"] + type: str """ - Fulfillment method type. + Fulfillment method type. Well-known values: `shipping`, `pickup`. Businesses MAY use additional values. """ line_item_ids: list[str] """ @@ -49,11 +47,11 @@ class FulfillmentMethod(BaseModel): list[fulfillment_destination.FulfillmentDestination] | None ) = None """ - Available destinations. For shipping: addresses. For pickup: retail locations. + Available destinations for this method. In Business responses, each destination carries a `type` and `id`. """ selected_destination_id: str | None = None """ - ID of the selected destination. + ID of the selected destination. Accepts any stable, Business-scoped ID the Business recognizes for this method, including Location IDs not yet enumerated in `destinations`. """ groups: list[fulfillment_group.FulfillmentGroup] | None = None """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_create_request.py index 602c48d..981c8cd 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_create_request.py @@ -18,44 +18,26 @@ from __future__ import annotations -from typing import Literal - from pydantic import BaseModel, ConfigDict -from . import ( - fulfillment_destination_create_request, - fulfillment_group_create_request, -) +from . import fulfillment_group_create_request class FulfillmentMethodCreateRequest(BaseModel): """ - A fulfillment method (shipping or pickup) with destinations and groups. + A fulfillment method with destinations and groups. """ model_config = ConfigDict( extra="allow", ) - type: Literal["shipping", "pickup"] - """ - Fulfillment method type. - """ - line_item_ids: list[str] | None = None - """ - Line item IDs fulfilled via this method. - """ - destinations: ( - list[ - fulfillment_destination_create_request.FulfillmentDestinationCreateRequest - ] - | None - ) = None + type: str """ - Available destinations. For shipping: addresses. For pickup: retail locations. + Fulfillment method type. Well-known values: `shipping`, `pickup`. Businesses MAY use additional values. """ selected_destination_id: str | None = None """ - ID of the selected destination. + ID of the selected destination. Accepts any stable, Business-scoped ID the Business recognizes for this method, including Location IDs not yet enumerated in `destinations`. """ groups: ( list[fulfillment_group_create_request.FulfillmentGroupCreateRequest] diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_request.py index 712477f..3f06a19 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_request.py @@ -18,19 +18,14 @@ from __future__ import annotations -from typing import Literal - from pydantic import BaseModel, ConfigDict -from . import ( - fulfillment_destination_update_request, - fulfillment_group_update_request, -) +from . import fulfillment_group_update_request class FulfillmentMethodUpdateRequest(BaseModel): """ - A fulfillment method (shipping or pickup) with destinations and groups. + A fulfillment method with destinations and groups. """ model_config = ConfigDict( @@ -40,26 +35,17 @@ class FulfillmentMethodUpdateRequest(BaseModel): """ Unique fulfillment method identifier. """ - type: Literal["shipping", "pickup"] | None = None + type: str | None = None """ - Fulfillment method type. + Fulfillment method type. Well-known values: `shipping`, `pickup`. Businesses MAY use additional values. """ line_item_ids: list[str] """ Line item IDs fulfilled via this method. """ - destinations: ( - list[ - fulfillment_destination_update_request.FulfillmentDestinationUpdateRequest - ] - | None - ) = None - """ - Available destinations. For shipping: addresses. For pickup: retail locations. - """ selected_destination_id: str | None = None """ - ID of the selected destination. + ID of the selected destination. Accepts any stable, Business-scoped ID the Business recognizes for this method, including Location IDs not yet enumerated in `destinations`. """ groups: ( list[fulfillment_group_update_request.FulfillmentGroupUpdateRequest] diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option.py index 93b3bf8..b5f3ff1 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option.py @@ -18,31 +18,20 @@ from __future__ import annotations -from pydantic import AwareDatetime, BaseModel, ConfigDict +from pydantic import AwareDatetime, ConfigDict -from . import total +from ...common.types import total +from .fulfillment_option_base import FulfillmentOptionBase -class FulfillmentOption(BaseModel): +class FulfillmentOption(FulfillmentOptionBase): """ - A fulfillment option within a group (e.g., Standard Shipping $5, Express $15). + A fulfillment option within a group (e.g., Standard Shipping $5, Express $15). Extends the fulfillment option base with cost and timing. """ model_config = ConfigDict( extra="allow", ) - id: str - """ - Unique fulfillment option identifier. - """ - title: str - """ - Short label (e.g., 'Express Shipping', 'Curbside Pickup'). - """ - description: str | None = None - """ - Complete context for buyer decision (e.g., 'Arrives Dec 12-15 via FedEx'). - """ carrier: str | None = None """ Carrier name (for shipping). diff --git a/src/ucp_sdk/models/schemas/shopping/types/merchant_fulfillment_config.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_base.py similarity index 54% rename from src/ucp_sdk/models/schemas/shopping/types/merchant_fulfillment_config.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_base.py index f4b1afd..b54b7de 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/merchant_fulfillment_config.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_base.py @@ -18,44 +18,28 @@ from __future__ import annotations -from typing import Literal - from pydantic import BaseModel, ConfigDict +from ...common.types import description as description_1 -class AllowsMultiDestination(BaseModel): - """ - Permits multiple destinations per method type. - """ - model_config = ConfigDict( - extra="forbid", - ) - shipping: bool | None = None - """ - Multiple shipping destinations allowed. - """ - pickup: bool | None = None +class FulfillmentOptionBase(BaseModel): """ - Multiple pickup locations allowed. - """ - - -class MerchantFulfillmentConfig(BaseModel): - """ - Merchant's fulfillment configuration. + Common base for a fulfillment option: an addressable, renderable choice (e.g. Standard, Express). Catalog uses this base directly; checkout composes it with cost and timing. """ model_config = ConfigDict( extra="allow", ) - allows_multi_destination: AllowsMultiDestination | None = None + id: str + """ + Unique identifier for this fulfillment option. + """ + title: str """ - Permits multiple destinations per method type. + Short label that distinguishes this option from its siblings (e.g. 'Standard', 'Express Shipping', 'Curbside Pickup'). """ - allows_method_combinations: ( - list[list[Literal["shipping", "pickup"]]] | None - ) = None + description: description_1.Description | None = None """ - Allowed method type combinations. + Supplementary context for the title (e.g. 'Arrives in 4 business days', 'Arrives Dec 12-15 via FedEx'). Directly renderable; MUST NOT repeat the title. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_base_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_base_create_request.py new file mode 100644 index 0000000..5431e66 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_base_create_request.py @@ -0,0 +1,31 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class FulfillmentOptionBaseCreateRequest(BaseModel): + """ + Common base for a fulfillment option: an addressable, renderable choice (e.g. Standard, Express). Catalog uses this base directly; checkout composes it with cost and timing. + """ + + model_config = ConfigDict( + extra="allow", + ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_base_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_base_update_request.py new file mode 100644 index 0000000..b0112af --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_base_update_request.py @@ -0,0 +1,31 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class FulfillmentOptionBaseUpdateRequest(BaseModel): + """ + Common base for a fulfillment option: an addressable, renderable choice (e.g. Standard, Express). Catalog uses this base directly; checkout composes it with cost and timing. + """ + + model_config = ConfigDict( + extra="allow", + ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_create_request.py index d08ee75..4cbda60 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_create_request.py @@ -18,12 +18,16 @@ from __future__ import annotations -from pydantic import BaseModel, ConfigDict +from pydantic import ConfigDict +from .fulfillment_option_base_create_request import ( + FulfillmentOptionBaseCreateRequest, +) -class FulfillmentOptionCreateRequest(BaseModel): + +class FulfillmentOptionCreateRequest(FulfillmentOptionBaseCreateRequest): """ - A fulfillment option within a group (e.g., Standard Shipping $5, Express $15). + A fulfillment option within a group (e.g., Standard Shipping $5, Express $15). Extends the fulfillment option base with cost and timing. """ model_config = ConfigDict( diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_update_request.py index 363e7f3..d2928a7 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_update_request.py @@ -18,12 +18,16 @@ from __future__ import annotations -from pydantic import BaseModel, ConfigDict +from pydantic import ConfigDict +from .fulfillment_option_base_update_request import ( + FulfillmentOptionBaseUpdateRequest, +) -class FulfillmentOptionUpdateRequest(BaseModel): + +class FulfillmentOptionUpdateRequest(FulfillmentOptionBaseUpdateRequest): """ - A fulfillment option within a group (e.g., Standard Shipping $5, Express $15). + A fulfillment option within a group (e.g., Standard Shipping $5, Express $15). Extends the fulfillment option base with cost and timing. """ model_config = ConfigDict( diff --git a/src/ucp_sdk/models/schemas/shopping/types/item.py b/src/ucp_sdk/models/schemas/shopping/types/item.py index cfcc71c..1d07c83 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/item.py +++ b/src/ucp_sdk/models/schemas/shopping/types/item.py @@ -20,7 +20,9 @@ from pydantic import AnyUrl, BaseModel, ConfigDict -from . import amount +from ...common.types import amount +from ...common.types import quantity_unit as quantity_unit_1 +from . import unit_price as unit_price_1 class Item(BaseModel): @@ -37,7 +39,15 @@ class Item(BaseModel): """ price: amount.Amount """ - Unit price in ISO 4217 minor units. + Unit price in ISO 4217 minor units. Price is the amount per one whole `quantity_unit.unit` (for example, per lb or per hour); when `quantity_unit` is absent, it is per `each`. + """ + quantity_unit: quantity_unit_1.QuantityUnit | None = None + """ + Sale basis this item's `quantity` is denominated in. On an authoritative Business response, absence encodes the default `each` machine identity (`C62`, 0); the Business MUST include this descriptor for every non-`each` response. On Platform requests, omission makes no assertion: the Business interprets `quantity` using the item's authoritative sale basis. If the Platform includes this descriptor, it asserts the unit-descriptor machine identity. The Business MUST compare that machine identity (`unit`, effective `scale`), ignore `display_text` and `increment`, and resolve a mismatch by conversion surfaced as a visible line revision with a warning, or by rejection with a recoverable business outcome; silent reinterpretation is forbidden. An explicit `C62` descriptor at effective scale 0 matches an authoritative basis represented by an absent descriptor. + """ + unit_price: unit_price_1.UnitPrice | None = None + """ + Pricing basis for this item. On an authoritative Business response, the Business MUST include `unit_price` on every line whose pricing basis differs from its sale basis (for example, priced per pound but sold per `each`); presence on a line marks the rate as transactional rather than display-only. When the pricing basis is the sale basis, `item.price` fully denominates the charge and this field MAY be omitted. """ image_url: AnyUrl | None = None """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/item_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/item_create_request.py index bbef8e8..df9f0cd 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/item_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/item_create_request.py @@ -20,6 +20,8 @@ from pydantic import BaseModel, ConfigDict +from ...common.types import quantity_unit_create_request + class ItemCreateRequest(BaseModel): model_config = ConfigDict( @@ -29,3 +31,9 @@ class ItemCreateRequest(BaseModel): """ The product identifier, often the SKU, required to resolve the product details associated with this line item. Should be recognized by both the Platform, and the Business. """ + quantity_unit: ( + quantity_unit_create_request.QuantityUnitCreateRequest | None + ) = None + """ + Sale basis this item's `quantity` is denominated in. On an authoritative Business response, absence encodes the default `each` machine identity (`C62`, 0); the Business MUST include this descriptor for every non-`each` response. On Platform requests, omission makes no assertion: the Business interprets `quantity` using the item's authoritative sale basis. If the Platform includes this descriptor, it asserts the unit-descriptor machine identity. The Business MUST compare that machine identity (`unit`, effective `scale`), ignore `display_text` and `increment`, and resolve a mismatch by conversion surfaced as a visible line revision with a warning, or by rejection with a recoverable business outcome; silent reinterpretation is forbidden. An explicit `C62` descriptor at effective scale 0 matches an authoritative basis represented by an absent descriptor. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/item_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/item_update_request.py index c957d16..96186f5 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/item_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/item_update_request.py @@ -20,6 +20,8 @@ from pydantic import BaseModel, ConfigDict +from ...common.types import quantity_unit_update_request + class ItemUpdateRequest(BaseModel): model_config = ConfigDict( @@ -29,3 +31,9 @@ class ItemUpdateRequest(BaseModel): """ The product identifier, often the SKU, required to resolve the product details associated with this line item. Should be recognized by both the Platform, and the Business. """ + quantity_unit: ( + quantity_unit_update_request.QuantityUnitUpdateRequest | None + ) = None + """ + Sale basis this item's `quantity` is denominated in. On an authoritative Business response, absence encodes the default `each` machine identity (`C62`, 0); the Business MUST include this descriptor for every non-`each` response. On Platform requests, omission makes no assertion: the Business interprets `quantity` using the item's authoritative sale basis. If the Platform includes this descriptor, it asserts the unit-descriptor machine identity. The Business MUST compare that machine identity (`unit`, effective `scale`), ignore `display_text` and `increment`, and resolve a mismatch by conversion surfaced as a visible line revision with a warning, or by rejection with a recoverable business outcome; silent reinterpretation is forbidden. An explicit `C62` descriptor at effective scale 0 matches an authoritative basis represented by an absent descriptor. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/line_item.py b/src/ucp_sdk/models/schemas/shopping/types/line_item.py index 9f2befe..cf31595 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/line_item.py +++ b/src/ucp_sdk/models/schemas/shopping/types/line_item.py @@ -20,8 +20,8 @@ from pydantic import BaseModel, ConfigDict, Field +from ...common.types import total from . import item as item_1 -from . import total class LineItem(BaseModel): @@ -34,9 +34,9 @@ class LineItem(BaseModel): ) id: str item: item_1.Item - quantity: int = Field(..., ge=1) + quantity: int = Field(..., ge=1, le=9007199254740991) """ - Quantity of the item being purchased. + Always an integer step count. On Platform requests, steps use the item's Business-authoritative sale basis; omitting `item.quantity_unit` makes no assertion and does not imply `each`. On Business responses, `item.quantity_unit` describes the basis; if absent, it encodes the `each` machine identity (`C62`, 0) and `quantity` counts whole items. """ totals: list[total.Total] """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/line_item_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/line_item_create_request.py index 754e4fe..c669bb3 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/line_item_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/line_item_create_request.py @@ -32,7 +32,7 @@ class LineItemCreateRequest(BaseModel): extra="allow", ) item: item_create_request.ItemCreateRequest - quantity: int = Field(..., ge=1) + quantity: int = Field(..., ge=1, le=9007199254740991) """ - Quantity of the item being purchased. + Always an integer step count. On Platform requests, steps use the item's Business-authoritative sale basis; omitting `item.quantity_unit` makes no assertion and does not imply `each`. On Business responses, `item.quantity_unit` describes the basis; if absent, it encodes the `each` machine identity (`C62`, 0) and `quantity` counts whole items. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/line_item_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/line_item_update_request.py index fc43593..a5c78e4 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/line_item_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/line_item_update_request.py @@ -33,9 +33,9 @@ class LineItemUpdateRequest(BaseModel): ) id: str | None = None item: item_update_request.ItemUpdateRequest - quantity: int = Field(..., ge=1) + quantity: int = Field(..., ge=1, le=9007199254740991) """ - Quantity of the item being purchased. + Always an integer step count. On Platform requests, steps use the item's Business-authoritative sale basis; omitting `item.quantity_unit` makes no assertion and does not imply `each`. On Business responses, `item.quantity_unit` describes the basis; if absent, it encodes the `each` machine identity (`C62`, 0) and `quantity` counts whole items. """ parent_id: str | None = None """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/location_destination.py b/src/ucp_sdk/models/schemas/shopping/types/location_destination.py new file mode 100644 index 0000000..9860bac --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/location_destination.py @@ -0,0 +1,39 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Literal + +from pydantic import ConfigDict + +from ...common.types.location_summary import LocationSummary + + +class BusinessLocationDestination(LocationSummary): + """ + A business location fulfillment destination. Business-authored and response-only: the Platform selects a location via `selected_destination_id` rather than writing destinations. + """ + + model_config = ConfigDict( + extra="allow", + ) + type: Literal["business_location"] + """ + Destination type discriminator. Response-only. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/location_destination_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/location_destination_create_request.py new file mode 100644 index 0000000..90cb2a5 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/location_destination_create_request.py @@ -0,0 +1,35 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict + +from ...common.types.location_summary_create_request import ( + LocationSummaryCreateRequest, +) + + +class BusinessLocationDestinationCreateRequest(LocationSummaryCreateRequest): + """ + A business location fulfillment destination. Business-authored and response-only: the Platform selects a location via `selected_destination_id` rather than writing destinations. + """ + + model_config = ConfigDict( + extra="allow", + ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/location_destination_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/location_destination_update_request.py new file mode 100644 index 0000000..3154a71 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/location_destination_update_request.py @@ -0,0 +1,35 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict + +from ...common.types.location_summary_update_request import ( + LocationSummaryUpdateRequest, +) + + +class BusinessLocationDestinationUpdateRequest(LocationSummaryUpdateRequest): + """ + A business location fulfillment destination. Business-authored and response-only: the Platform selects a location via `selected_destination_id` rather than writing destinations. + """ + + model_config = ConfigDict( + extra="allow", + ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/order_line_item.py b/src/ucp_sdk/models/schemas/shopping/types/order_line_item.py index cd80f04..131c550 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/order_line_item.py +++ b/src/ucp_sdk/models/schemas/shopping/types/order_line_item.py @@ -22,29 +22,29 @@ from pydantic import BaseModel, ConfigDict, Field +from ...common.types import total as total_1 from . import item as item_1 -from . import total as total_1 class Quantity(BaseModel): """ - Quantity tracking for the line item. + Tracks the line item's original, current active, and fulfilled quantities. All three values use the same inherited `item.quantity_unit`. When `item.quantity_unit` is absent on an authoritative order response, each step is one whole item (`each`) under the shared default. """ model_config = ConfigDict( extra="allow", ) - original: int | None = Field(None, ge=0) + original: int | None = Field(None, ge=0, le=9007199254740991) """ - Quantity from the original checkout. + Quantity from the original checkout, expressed as an integer step count. """ - total: int = Field(..., ge=0) + total: int = Field(..., ge=0, le=9007199254740991) """ - Current total active quantity. May differ from original due to post-order modifications (e.g., returns or cancellations). + Current active quantity after returns, cancellations, or other order changes, expressed as an integer step count. """ - fulfilled: int = Field(..., ge=0) + fulfilled: int = Field(..., ge=0, le=9007199254740991) """ - Quantity fulfilled so far. + Quantity fulfilled so far, expressed as an integer step count. """ @@ -58,11 +58,11 @@ class OrderLineItem(BaseModel): """ item: item_1.Item """ - Product data (id, title, price, image_url). + Purchased item data, including identity, price, and sale basis. """ quantity: Quantity """ - Quantity tracking for the line item. + Tracks the line item's original, current active, and fulfilled quantities. All three values use the same inherited `item.quantity_unit`. When `item.quantity_unit` is absent on an authoritative order response, each step is one whole item (`each`) under the shared default. """ totals: list[total_1.Total] """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/order_line_item_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/order_line_item_create_request.py index 56693e0..6e0fe34 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/order_line_item_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/order_line_item_create_request.py @@ -22,28 +22,29 @@ from pydantic import BaseModel, ConfigDict, Field -from . import item_create_request, total_create_request +from ...common.types import total_create_request +from . import item_create_request class Quantity(BaseModel): """ - Quantity tracking for the line item. + Tracks the line item's original, current active, and fulfilled quantities. All three values use the same inherited `item.quantity_unit`. When `item.quantity_unit` is absent on an authoritative order response, each step is one whole item (`each`) under the shared default. """ model_config = ConfigDict( extra="allow", ) - original: int | None = Field(None, ge=0) + original: int | None = Field(None, ge=0, le=9007199254740991) """ - Quantity from the original checkout. + Quantity from the original checkout, expressed as an integer step count. """ - total: int = Field(..., ge=0) + total: int = Field(..., ge=0, le=9007199254740991) """ - Current total active quantity. May differ from original due to post-order modifications (e.g., returns or cancellations). + Current active quantity after returns, cancellations, or other order changes, expressed as an integer step count. """ - fulfilled: int = Field(..., ge=0) + fulfilled: int = Field(..., ge=0, le=9007199254740991) """ - Quantity fulfilled so far. + Quantity fulfilled so far, expressed as an integer step count. """ @@ -57,11 +58,11 @@ class OrderLineItemCreateRequest(BaseModel): """ item: item_create_request.ItemCreateRequest """ - Product data (id, title, price, image_url). + Purchased item data, including identity, price, and sale basis. """ quantity: Quantity """ - Quantity tracking for the line item. + Tracks the line item's original, current active, and fulfilled quantities. All three values use the same inherited `item.quantity_unit`. When `item.quantity_unit` is absent on an authoritative order response, each step is one whole item (`each`) under the shared default. """ totals: list[total_create_request.TotalCreateRequest] """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/order_line_item_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/order_line_item_update_request.py index 178d09c..7db84e2 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/order_line_item_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/order_line_item_update_request.py @@ -22,28 +22,29 @@ from pydantic import BaseModel, ConfigDict, Field -from . import item_update_request, total_update_request +from ...common.types import total_update_request +from . import item_update_request class Quantity(BaseModel): """ - Quantity tracking for the line item. + Tracks the line item's original, current active, and fulfilled quantities. All three values use the same inherited `item.quantity_unit`. When `item.quantity_unit` is absent on an authoritative order response, each step is one whole item (`each`) under the shared default. """ model_config = ConfigDict( extra="allow", ) - original: int | None = Field(None, ge=0) + original: int | None = Field(None, ge=0, le=9007199254740991) """ - Quantity from the original checkout. + Quantity from the original checkout, expressed as an integer step count. """ - total: int = Field(..., ge=0) + total: int = Field(..., ge=0, le=9007199254740991) """ - Current total active quantity. May differ from original due to post-order modifications (e.g., returns or cancellations). + Current active quantity after returns, cancellations, or other order changes, expressed as an integer step count. """ - fulfilled: int = Field(..., ge=0) + fulfilled: int = Field(..., ge=0, le=9007199254740991) """ - Quantity fulfilled so far. + Quantity fulfilled so far, expressed as an integer step count. """ @@ -57,11 +58,11 @@ class OrderLineItemUpdateRequest(BaseModel): """ item: item_update_request.ItemUpdateRequest """ - Product data (id, title, price, image_url). + Purchased item data, including identity, price, and sale basis. """ quantity: Quantity """ - Quantity tracking for the line item. + Tracks the line item's original, current active, and fulfilled quantities. All three values use the same inherited `item.quantity_unit`. When `item.quantity_unit` is absent on an authoritative order response, each step is one whole item (`each`) under the shared default. """ totals: list[total_update_request.TotalUpdateRequest] """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/product.py b/src/ucp_sdk/models/schemas/shopping/types/product.py index 6fa39d0..57b9363 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/product.py +++ b/src/ucp_sdk/models/schemas/shopping/types/product.py @@ -22,11 +22,10 @@ from pydantic import AnyUrl, BaseModel, ConfigDict, Field -from . import category -from . import description as description_1 -from . import media as media_1 -from . import price_range as price_range_1 -from . import product_option +from ...common.types import description as description_1 +from ...common.types import media as media_1 +from ...common.types import price_range as price_range_1 +from . import category, product_option from . import rating as rating_1 from . import variant diff --git a/src/ucp_sdk/models/schemas/shopping/types/search_filters.py b/src/ucp_sdk/models/schemas/shopping/types/search_filters.py index 6b35cd2..c57be17 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/search_filters.py +++ b/src/ucp_sdk/models/schemas/shopping/types/search_filters.py @@ -20,7 +20,7 @@ from pydantic import BaseModel, ConfigDict -from . import price_filter +from ...common.types import price_filter class SearchFilters(BaseModel): diff --git a/src/ucp_sdk/models/schemas/shopping/types/shipping_destination.py b/src/ucp_sdk/models/schemas/shopping/types/shipping_destination.py index f8b929d..f5abc26 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/shipping_destination.py +++ b/src/ucp_sdk/models/schemas/shopping/types/shipping_destination.py @@ -18,9 +18,11 @@ from __future__ import annotations +from typing import Literal + from pydantic import ConfigDict -from .postal_address import PostalAddress +from ...common.types.postal_address import PostalAddress class ShippingDestination(PostalAddress): @@ -35,3 +37,7 @@ class ShippingDestination(PostalAddress): """ ID specific to this shipping destination. """ + type: Literal["shipping_address"] + """ + Destination type discriminator. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_create_request.py index b454152..b654698 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_create_request.py @@ -18,9 +18,13 @@ from __future__ import annotations +from typing import Literal + from pydantic import ConfigDict -from .postal_address_create_request import PostalAddressCreateRequest +from ...common.types.postal_address_create_request import ( + PostalAddressCreateRequest, +) class ShippingDestinationCreateRequest(PostalAddressCreateRequest): @@ -35,3 +39,7 @@ class ShippingDestinationCreateRequest(PostalAddressCreateRequest): """ ID specific to this shipping destination. """ + type: Literal["shipping_address"] | None = None + """ + Destination type discriminator. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_update_request.py index 3ab1733..c3b5904 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_update_request.py @@ -18,9 +18,13 @@ from __future__ import annotations +from typing import Literal + from pydantic import ConfigDict -from .postal_address_update_request import PostalAddressUpdateRequest +from ...common.types.postal_address_update_request import ( + PostalAddressUpdateRequest, +) class ShippingDestinationUpdateRequest(PostalAddressUpdateRequest): @@ -35,3 +39,7 @@ class ShippingDestinationUpdateRequest(PostalAddressUpdateRequest): """ ID specific to this shipping destination. """ + type: Literal["shipping_address"] | None = None + """ + Destination type discriminator. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/unit_price.py b/src/ucp_sdk/models/schemas/shopping/types/unit_price.py new file mode 100644 index 0000000..9760096 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/unit_price.py @@ -0,0 +1,74 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field + +from ...common.types import amount as amount_1 +from ...common.types.measure import Measure as Measure_1 + + +class Measure(Measure_1): + """ + Product quantity in packaging/content (for example, a 750 mL bottle), distinct from `quantity_unit`, which defines the sale basis. Its integer `value` MUST be at least 1. + """ + + model_config = ConfigDict( + extra="allow", + ) + value: Any | None = Field(None, ge=1) + + +class Reference(Measure_1): + """ + Denominator for unit price display (for example, per 100 mL or per 1 kg). Its integer `value` MUST be at least 1. + """ + + model_config = ConfigDict( + extra="allow", + ) + value: Any | None = Field(None, ge=1) + + +class UnitPrice(BaseModel): + """ + Price per standard unit of measurement. MAY be omitted when unit pricing does not apply. `unit_price.currency` MUST equal `price.currency`; the comparator MUST NOT perform currency conversion. `measure.unit` and `reference.unit` MUST be identical; cross-unit conversion is not permitted. Their scales MAY differ; each value represents `value × 10^-scale`. + """ + + model_config = ConfigDict( + extra="allow", + ) + amount: amount_1.Amount + """ + Unit price in ISO 4217 minor units. After satisfying the same-unit invariant, the Business MUST compute the comparator as `(price.amount / (measure.value × 10^-measure.scale)) × (reference.value × 10^-reference.scale)` and round it once to ISO 4217 minor units according to its pricing rules. The returned `unit_price.amount` is authoritative; the Platform MUST NOT recompute or substitute its own result. + """ + currency: str = Field(..., pattern="^[A-Z]{3}$") + """ + ISO 4217 currency code. + """ + measure: Measure + """ + Product quantity in packaging/content (for example, a 750 mL bottle), distinct from `quantity_unit`, which defines the sale basis. Its integer `value` MUST be at least 1. + """ + reference: Reference + """ + Denominator for unit price display (for example, per 100 mL or per 1 kg). Its integer `value` MUST be at least 1. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/variant.py b/src/ucp_sdk/models/schemas/shopping/types/variant.py index e45f245..4e93a4c 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/variant.py +++ b/src/ucp_sdk/models/schemas/shopping/types/variant.py @@ -20,16 +20,18 @@ from typing import Any -from pydantic import AnyUrl, BaseModel, ConfigDict, Field - -from . import amount as amount_1 +from pydantic import AnyUrl, BaseModel, ConfigDict + +from ...common.types import description as description_1 +from ...common.types import link +from ...common.types import media as media_1 +from ...common.types import price as price_1 +from ...common.types import quantity_unit as quantity_unit_1 +from . import availability as availability_1 from . import category -from . import description as description_1 -from . import link -from . import media as media_1 -from . import price as price_1 from . import rating as rating_1 from . import selected_option +from . import unit_price as unit_price_1 class Barcode(BaseModel): @@ -46,86 +48,6 @@ class Barcode(BaseModel): """ -class Measure(BaseModel): - """ - Product quantity in packaging (e.g., 750ml bottle). - """ - - model_config = ConfigDict( - extra="allow", - ) - value: float - """ - Package quantity. - """ - unit: str - """ - Unit of measurement. - """ - - -class Reference(BaseModel): - """ - Denominator for unit price display (e.g., per 100ml, per 1kg). - """ - - model_config = ConfigDict( - extra="allow", - ) - value: int - """ - Reference quantity. - """ - unit: str - """ - Unit of measurement. - """ - - -class UnitPrice(BaseModel): - """ - Price per standard unit of measurement. MAY be omitted when unit pricing does not apply. - """ - - model_config = ConfigDict( - extra="allow", - ) - amount: amount_1.Amount - """ - Unit price in ISO 4217 minor units. Business MUST return precomputed unit price value: (variant.price / measure.value) * reference.value. - """ - currency: str = Field(..., pattern="^[A-Z]{3}$") - """ - ISO 4217 currency code. - """ - measure: Measure - """ - Product quantity in packaging (e.g., 750ml bottle). - """ - reference: Reference - """ - Denominator for unit price display (e.g., per 100ml, per 1kg). - """ - - -class Availability(BaseModel): - """ - Variant availability for purchase. - """ - - model_config = ConfigDict( - extra="allow", - ) - available: bool | None = None - """ - Whether this variant can be purchased. See status for fulfillment details. - """ - status: str | None = None - """ - Qualifies available with fulfillment state. Well-known values: `in_stock`, `backorder`, `preorder`, `out_of_stock`, `discontinued`. - """ - - class Seller(BaseModel): """ Optional seller context for this variant. @@ -186,17 +108,21 @@ class Variant(BaseModel): """ price: price_1.Price """ - Current selling price. + Current selling price. Price is the amount per one whole `quantity_unit.unit` (for example, per lb or per hour); when `quantity_unit` is absent, it is per `each`. Line total is `price × quantity × 10^-scale`, computed and rounded once by the Business; `totals` remain authoritative. + """ + quantity_unit: quantity_unit_1.QuantityUnit | None = None + """ + Sale basis this variant's `quantity` is denominated in. The default sale basis is `each`, whose machine identity is (`C62`, 0); `C62` is the UN/CEFACT Rec20 code for one/each. An absent catalog descriptor encodes that default. An `increment` advertises the ordering granularity in steps (for example, `scale` 2 with `increment` 25 sells in 0.25-unit multiples). """ list_price: price_1.Price | None = None """ List price before discounts (for strikethrough display). """ - unit_price: UnitPrice | None = None + unit_price: unit_price_1.UnitPrice | None = None """ - Price per standard unit of measurement. MAY be omitted when unit pricing does not apply. + Price per standard unit of measurement, for shelf-style comparison display. MAY be omitted when unit pricing does not apply. """ - availability: Availability | None = None + availability: availability_1.Availability | None = None """ Variant availability for purchase. """ diff --git a/src/ucp_sdk/models/schemas/transports/a2a_message.py b/src/ucp_sdk/models/schemas/transports/a2a_message.py new file mode 100644 index 0000000..618f658 --- /dev/null +++ b/src/ucp_sdk/models/schemas/transports/a2a_message.py @@ -0,0 +1,133 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Annotated, Any, Literal + +from pydantic import AnyUrl, BaseModel, ConfigDict, Field +from typing_extensions import TypeAliasType + +from .jsonrpc import Request, SuccessResponse + + +class Extension(BaseModel): + """ + A2A Agent Card extension advertisement for UCP. + """ + + model_config = ConfigDict( + extra="allow", + ) + uri: AnyUrl + """ + Extension URI. UCP uses its versioned reference URI. + """ + description: str | None = None + params: dict[str, Any] | None = None + """ + Extension parameters such as advertised UCP capabilities. + """ + + +class AgentCard(BaseModel): + """ + A2A Agent Card fragment advertising UCP support through extensions. + """ + + model_config = ConfigDict( + extra="allow", + ) + extensions: list[Extension] = Field(..., min_length=1) + + +class Part(BaseModel): + """ + A2A message part. UCP examples use text parts for natural language and data parts for structured UCP payloads. + """ + + model_config = ConfigDict( + extra="allow", + ) + type: str | None = None + kind: str | None = None + text: str | None = None + data: dict[str, Any] | None = None + """ + Structured data payload. UCP reserves a2a.ucp.* keys for UCP payloads. + """ + + +class Message(BaseModel): + """ + A2A Message carrying natural-language or structured UCP data parts. + """ + + model_config = ConfigDict( + extra="allow", + ) + role: Literal["user", "agent"] + """ + Message sender role. + """ + parts: list[Part] = Field(..., min_length=1) + messageId: str + kind: Literal["message"] + contextId: str + + +class Params(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + message: Message + + +class MessageRequest(Request): + """ + A2A message/send JSON-RPC request whose params carry a UCP-bearing Message from the platform to the business agent. + """ + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["message/send"] | None = None + params: Params + + +class MessageResponse(SuccessResponse): + """ + JSON-RPC success response whose result is an A2A Message from the business agent. + """ + + model_config = ConfigDict( + extra="allow", + ) + result: Message | None = None + + +A2AUcpMessageEnvelope = TypeAliasType( + "A2AUcpMessageEnvelope", + Annotated[ + AgentCard | MessageRequest | MessageResponse, + Field(..., title="A2A UCP Message Envelope"), + ], +) +""" +Minimal A2A envelope shapes used by UCP's A2A checkout binding. This schema validates UCP's transport mapping points — Agent Card extension advertisement, inbound A2A Message requests, and JSON-RPC responses carrying A2A Message results — without attempting to re-specify the full A2A protocol. +""" diff --git a/src/ucp_sdk/models/schemas/transports/embedded_message.py b/src/ucp_sdk/models/schemas/transports/embedded_message.py new file mode 100644 index 0000000..7ffc673 --- /dev/null +++ b/src/ucp_sdk/models/schemas/transports/embedded_message.py @@ -0,0 +1,109 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Annotated, Any + +from pydantic import ConfigDict, Field, model_validator +from typing_extensions import TypeAliasType + +from . import jsonrpc +from .jsonrpc import Request as Request_1 +from .jsonrpc import SuccessResponse + +Method = TypeAliasType( + "Method", + Annotated[ + str, + Field( + ..., + pattern="^(ec|ep\\.cart)\\.[a-z][a-z0-9_]*(?:\\.[a-z][a-z0-9_]*)*$", + ), + ], +) +""" +Embedded Protocol method name. Checkout methods use ec.* and cart methods use ep.cart.*. +""" + + +class Request(Request_1): + """ + Embedded Protocol request or notification envelope. Messages with id expect a response; messages without id are notifications. + """ + + model_config = ConfigDict( + extra="allow", + ) + method: Method | None = None + params: dict[str, Any] + """ + Capability-specific EP parameters. + """ + + +class Response(SuccessResponse): + """ + Embedded Protocol success response envelope. UCP application-level success and error outcomes are both carried in result.ucp.status. + """ + + model_config = ConfigDict( + extra="allow", + ) + result: dict[str, Any] | None = None + """ + Capability-specific EP result. Application-level status is defined by capability schemas; see Embedded Protocol response handling. + """ + + @model_validator(mode="after") + def _enforce_conditional_required(self): + """JSON Schema if/then: enforce conditionally required fields.""" + rules = [ + { + "discriminator": "has_next_page", + "values": [True], + "required": ["cursor"], + } + ] + for rule in rules: + if getattr(self, rule["discriminator"], None) not in rule["values"]: + continue + for field in rule["required"]: + if field not in self.model_fields_set: + raise ValueError( + f"Field {field!r} is required by a schema condition" + ) + return self + + +ErrorResponse = TypeAliasType("ErrorResponse", jsonrpc.ErrorResponse) +""" +JSON-RPC transport-level error response for EP messages. Application-level failures use the response result with result.ucp.status=error instead. +""" + + +EmbeddedProtocolMessageEnvelope = TypeAliasType( + "EmbeddedProtocolMessageEnvelope", + Annotated[ + Request | Response | ErrorResponse, + Field(..., title="Embedded Protocol Message Envelope"), + ], +) +""" +JSON-RPC envelope for UCP Embedded Protocol (EP) messages exchanged between a host and an embedded context. This schema constrains the shared transport envelope and method namespace while leaving capability-specific params and result payloads to their capability schemas. +""" diff --git a/src/ucp_sdk/models/schemas/transports/jsonrpc.py b/src/ucp_sdk/models/schemas/transports/jsonrpc.py new file mode 100644 index 0000000..f8ffdd3 --- /dev/null +++ b/src/ucp_sdk/models/schemas/transports/jsonrpc.py @@ -0,0 +1,121 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Annotated, Any, Literal + +from pydantic import BaseModel, ConfigDict, Field +from typing_extensions import TypeAliasType + +Id = TypeAliasType("Id", str | float | None) +""" +JSON-RPC request identifier. Notifications omit id; responses echo the request id, or use null when the request id could not be determined. +""" + + +class Error(BaseModel): + """ + JSON-RPC transport-level error object. UCP business outcomes use result payloads with UCP messages instead of this object. + """ + + model_config = ConfigDict( + extra="allow", + ) + code: int + """ + JSON-RPC error code. Standard codes are negative integers; UCP bindings reserve business errors for UCP messages. + """ + message: str + """ + Short transport-level error description. + """ + data: Any | None = None + """ + Optional machine-readable transport error details. + """ + + +class Request(BaseModel): + """ + JSON-RPC request or notification envelope. Presence of id makes the message a request; absence of id makes it a notification. + """ + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + """ + JSON-RPC protocol version. + """ + id: Id | None = None + method: str = Field(..., min_length=1) + """ + Transport method name. Binding-specific schemas constrain the method namespace. + """ + params: dict[str, Any] | list[Any] | None = None + """ + Method parameters. Binding-specific schemas define the object shape. + """ + + +class SuccessResponse(BaseModel): + """ + JSON-RPC success response envelope. + """ + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + """ + JSON-RPC protocol version. + """ + id: Id + result: Any + """ + Successful transport result. UCP bindings define the nested result payload. + """ + + +class ErrorResponse(BaseModel): + """ + JSON-RPC transport error response envelope. This is for protocol-level failures, not UCP application-level messages. + """ + + model_config = ConfigDict( + extra="forbid", + ) + jsonrpc: Literal["2.0"] + """ + JSON-RPC protocol version. + """ + id: Id + error: Error + + +JsonRpc20Envelope = TypeAliasType( + "JsonRpc20Envelope", + Annotated[ + Request | SuccessResponse | ErrorResponse, + Field(..., title="JSON-RPC 2.0 Envelope"), + ], +) +""" +Common JSON-RPC 2.0 transport envelope used by UCP JSON-RPC-based bindings. This schema intentionally validates only the protocol envelope; binding-specific params and result payloads are validated by transport-specific schemas or extracted UCP payload schemas. +""" diff --git a/src/ucp_sdk/models/schemas/transports/mcp_tool_call.py b/src/ucp_sdk/models/schemas/transports/mcp_tool_call.py new file mode 100644 index 0000000..6418787 --- /dev/null +++ b/src/ucp_sdk/models/schemas/transports/mcp_tool_call.py @@ -0,0 +1,157 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Annotated, Any, Literal + +from pydantic import BaseModel, ConfigDict, Field, model_validator +from typing_extensions import TypeAliasType + +from . import jsonrpc +from .jsonrpc import Request as Request_1 +from .jsonrpc import SuccessResponse + + +class UcpAgent(BaseModel): + """ + UCP-Agent metadata carried inside MCP tool arguments. + """ + + model_config = ConfigDict( + extra="allow", + ) + profile: str + """ + Platform profile URI advertised to the business. + """ + + +class Meta(BaseModel): + """ + UCP request metadata passed through MCP params.arguments.meta. + """ + + model_config = ConfigDict( + extra="allow", + ) + ucp_agent: UcpAgent | None = Field(None, alias="ucp-agent") + idempotency_key: str | None = Field(None, alias="idempotency-key") + """ + Optional idempotency key for retry-safe mutating operations. + """ + + +class Arguments(BaseModel): + """ + MCP tool arguments. UCP reserves meta for transport metadata; operation payload fields such as checkout, cart, order id, or catalog inputs are operation-specific. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Meta | None = None + + +class Params(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + name: str = Field(..., min_length=1) + """ + MCP tool name matching the UCP operation binding, such as create_checkout or get_cart. + """ + arguments: Arguments + + +class Request(Request_1): + """ + MCP tools/call request envelope for invoking a UCP operation. + """ + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["tools/call"] | None = None + params: Params + + +class ContentPart(BaseModel): + """ + MCP content part returned for clients that do not consume structuredContent. + """ + + model_config = ConfigDict( + extra="allow", + ) + type: str + text: str | None = None + + +class Result(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + structuredContent: dict[str, Any] + """ + The UCP response payload for the operation. + """ + content: list[ContentPart] | None = None + + +class Response(SuccessResponse): + """ + MCP tools/call response envelope. UCP payloads are carried in result.structuredContent; content is compatibility output. + """ + + model_config = ConfigDict( + extra="allow", + ) + result: Result | None = None + + @model_validator(mode="after") + def _enforce_conditional_required(self): + """JSON Schema if/then: enforce conditionally required fields.""" + rules = [ + { + "discriminator": "has_next_page", + "values": [True], + "required": ["cursor"], + } + ] + for rule in rules: + if getattr(self, rule["discriminator"], None) not in rule["values"]: + continue + for field in rule["required"]: + if field not in self.model_fields_set: + raise ValueError( + f"Field {field!r} is required by a schema condition" + ) + return self + + +McpToolCallEnvelope = TypeAliasType( + "McpToolCallEnvelope", + Annotated[ + Request | Response | jsonrpc.ErrorResponse, + Field(..., title="MCP Tool Call Envelope"), + ], +) +""" +UCP's MCP transport envelope for JSON-RPC tools/call messages. The schema validates the MCP mapping layer: operation name in params.name, UCP metadata and domain arguments in params.arguments, and UCP output in result.structuredContent. +""" diff --git a/src/ucp_sdk/models/schemas/ucp.py b/src/ucp_sdk/models/schemas/ucp.py index 373f7a9..339727a 100644 --- a/src/ucp_sdk/models/schemas/ucp.py +++ b/src/ucp_sdk/models/schemas/ucp.py @@ -24,13 +24,14 @@ from typing_extensions import TypeAliasType from . import capability, payment_handler, service -from .shopping.types import reverse_domain_name +from .common.types import request_constraints as request_constraints_1 +from .common.types import reverse_domain_name Version = TypeAliasType( "Version", Annotated[str, Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$")] ) """ -UCP version in YYYY-MM-DD format. +Version identifier in YYYY-MM-DD format. """ @@ -62,7 +63,7 @@ class Requires(BaseModel): ) protocol: VersionConstraint | None = None """ - Required protocol version. + Required range for the selected `ucp.version`. """ capabilities: ( dict[reverse_domain_name.ReverseDomainName, VersionConstraint] | None @@ -72,6 +73,12 @@ class Requires(BaseModel): """ +MapOrder = TypeAliasType("MapOrder", dict[str, list[str]]) +""" +Preferred key order for map-valued fields in the scope annotated by the containing `ucp` member. Each property names a target map, and its array lists target keys in preferred order. Lists may be partial and are not allowlists. +""" + + class Entity(BaseModel): """ Shared foundation for all UCP entities. @@ -102,6 +109,18 @@ class Entity(BaseModel): """ +class Members(BaseModel): + """ + Members defined inside the reserved `ucp` protocol object. The object is open for forward compatibility: consumers MUST ignore unrecognized members. Only UCP core defines members, and every defined member MUST be safe to ignore. + """ + + model_config = ConfigDict( + extra="allow", + ) + map_order: MapOrder | None = None + request_constraints: request_constraints_1.RequestConstraints | None = None + + class Base(BaseModel): """ Base UCP metadata with shared properties for all schema types. @@ -111,6 +130,10 @@ class Base(BaseModel): extra="allow", ) version: Version + map_order: MapOrder | None = None + """ + Preferred key-traversal order for sibling registry fields inside the root `ucp` envelope (`services`, `capabilities`, and `payment_handlers`). + """ status: Literal["success", "error"] | None = "success" """ Application-level status of the UCP operation. @@ -174,7 +197,7 @@ class PlatformSchema(Base): extra="allow", ) services: dict[ - reverse_domain_name.ReverseDomainName, list[service.PlatformSchema5] + reverse_domain_name.ReverseDomainName, list[service.PlatformSchema6] ] """ Service registry keyed by reverse-domain name. @@ -211,7 +234,7 @@ class BusinessSchema(Base): Previous protocol versions this business supports, mapped to profile URIs. Businesses that support older protocol versions SHOULD advertise each version and link to its profile. Each URI points to a complete, self-contained profile for that version. When omitted, only `version` is supported. """ services: dict[ - reverse_domain_name.ReverseDomainName, list[service.BusinessSchema2] + reverse_domain_name.ReverseDomainName, list[service.BusinessSchema3] ] """ Service registry keyed by reverse-domain name. @@ -331,6 +354,26 @@ class ResponseCatalogSchema(Base): """ +class ResponseLocationSchema(Base): + """ + UCP metadata for location responses. + """ + + model_config = ConfigDict( + extra="allow", + ) + capabilities: ( + dict[ + reverse_domain_name.ReverseDomainName, + list[capability.ResponseSchema], + ] + | None + ) = None + """ + Capability registry keyed by reverse-domain name. + """ + + UcpMetadata = TypeAliasType( "UcpMetadata", Annotated[ @@ -339,7 +382,8 @@ class ResponseCatalogSchema(Base): | ResponseCheckoutSchema | ResponseOrderSchema | ResponseCartSchema - | ResponseCatalogSchema, + | ResponseCatalogSchema + | ResponseLocationSchema, Field(..., title="UCP Metadata"), ], ) diff --git a/src/ucp_sdk/models/schemas/ucp_create_request.py b/src/ucp_sdk/models/schemas/ucp_create_request.py index bba6e04..7ff439b 100644 --- a/src/ucp_sdk/models/schemas/ucp_create_request.py +++ b/src/ucp_sdk/models/schemas/ucp_create_request.py @@ -24,13 +24,14 @@ from typing_extensions import TypeAliasType from . import capability, payment_handler, service -from .shopping.types import reverse_domain_name_create_request +from .common.types import request_constraints as request_constraints_1 +from .common.types import reverse_domain_name_create_request Version = TypeAliasType( "Version", Annotated[str, Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$")] ) """ -UCP version in YYYY-MM-DD format. +Version identifier in YYYY-MM-DD format. """ @@ -62,7 +63,7 @@ class Requires(BaseModel): ) protocol: VersionConstraint | None = None """ - Required protocol version. + Required range for the selected `ucp.version`. """ capabilities: ( dict[ @@ -76,6 +77,12 @@ class Requires(BaseModel): """ +MapOrder = TypeAliasType("MapOrder", dict[str, list[str]]) +""" +Preferred key order for map-valued fields in the scope annotated by the containing `ucp` member. Each property names a target map, and its array lists target keys in preferred order. Lists may be partial and are not allowlists. +""" + + class Entity(BaseModel): """ Shared foundation for all UCP entities. @@ -106,6 +113,18 @@ class Entity(BaseModel): """ +class Members(BaseModel): + """ + Members defined inside the reserved `ucp` protocol object. The object is open for forward compatibility: consumers MUST ignore unrecognized members. Only UCP core defines members, and every defined member MUST be safe to ignore. + """ + + model_config = ConfigDict( + extra="allow", + ) + map_order: MapOrder | None = None + request_constraints: request_constraints_1.RequestConstraints | None = None + + class Base(BaseModel): """ Base UCP metadata with shared properties for all schema types. @@ -115,6 +134,10 @@ class Base(BaseModel): extra="allow", ) version: Version + map_order: MapOrder | None = None + """ + Preferred key-traversal order for sibling registry fields inside the root `ucp` envelope (`services`, `capabilities`, and `payment_handlers`). + """ status: Literal["success", "error"] | None = "success" """ Application-level status of the UCP operation. @@ -189,7 +212,7 @@ class PlatformSchema(Base): ) services: dict[ reverse_domain_name_create_request.ReverseDomainNameCreateRequest, - list[service.PlatformSchema5], + list[service.PlatformSchema6], ] """ Service registry keyed by reverse-domain name. @@ -227,7 +250,7 @@ class BusinessSchema(Base): """ services: dict[ reverse_domain_name_create_request.ReverseDomainNameCreateRequest, - list[service.BusinessSchema2], + list[service.BusinessSchema3], ] """ Service registry keyed by reverse-domain name. @@ -348,6 +371,26 @@ class ResponseCatalogSchema(Base): """ +class ResponseLocationSchema(Base): + """ + UCP metadata for location responses. + """ + + model_config = ConfigDict( + extra="allow", + ) + capabilities: ( + dict[ + reverse_domain_name_create_request.ReverseDomainNameCreateRequest, + list[capability.ResponseSchema], + ] + | None + ) = None + """ + Capability registry keyed by reverse-domain name. + """ + + UcpMetadataCreateRequest = TypeAliasType( "UcpMetadataCreateRequest", Annotated[ @@ -356,7 +399,8 @@ class ResponseCatalogSchema(Base): | ResponseCheckoutSchema | ResponseOrderSchema | ResponseCartSchema - | ResponseCatalogSchema, + | ResponseCatalogSchema + | ResponseLocationSchema, Field(..., title="UCP Metadata Create Request"), ], ) diff --git a/src/ucp_sdk/models/schemas/ucp_update_request.py b/src/ucp_sdk/models/schemas/ucp_update_request.py index 698fc4e..bfad2be 100644 --- a/src/ucp_sdk/models/schemas/ucp_update_request.py +++ b/src/ucp_sdk/models/schemas/ucp_update_request.py @@ -24,13 +24,14 @@ from typing_extensions import TypeAliasType from . import capability, payment_handler, service -from .shopping.types import reverse_domain_name_update_request +from .common.types import request_constraints as request_constraints_1 +from .common.types import reverse_domain_name_update_request Version = TypeAliasType( "Version", Annotated[str, Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$")] ) """ -UCP version in YYYY-MM-DD format. +Version identifier in YYYY-MM-DD format. """ @@ -62,7 +63,7 @@ class Requires(BaseModel): ) protocol: VersionConstraint | None = None """ - Required protocol version. + Required range for the selected `ucp.version`. """ capabilities: ( dict[ @@ -76,6 +77,12 @@ class Requires(BaseModel): """ +MapOrder = TypeAliasType("MapOrder", dict[str, list[str]]) +""" +Preferred key order for map-valued fields in the scope annotated by the containing `ucp` member. Each property names a target map, and its array lists target keys in preferred order. Lists may be partial and are not allowlists. +""" + + class Entity(BaseModel): """ Shared foundation for all UCP entities. @@ -106,6 +113,18 @@ class Entity(BaseModel): """ +class Members(BaseModel): + """ + Members defined inside the reserved `ucp` protocol object. The object is open for forward compatibility: consumers MUST ignore unrecognized members. Only UCP core defines members, and every defined member MUST be safe to ignore. + """ + + model_config = ConfigDict( + extra="allow", + ) + map_order: MapOrder | None = None + request_constraints: request_constraints_1.RequestConstraints | None = None + + class Base(BaseModel): """ Base UCP metadata with shared properties for all schema types. @@ -115,6 +134,10 @@ class Base(BaseModel): extra="allow", ) version: Version + map_order: MapOrder | None = None + """ + Preferred key-traversal order for sibling registry fields inside the root `ucp` envelope (`services`, `capabilities`, and `payment_handlers`). + """ status: Literal["success", "error"] | None = "success" """ Application-level status of the UCP operation. @@ -189,7 +212,7 @@ class PlatformSchema(Base): ) services: dict[ reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, - list[service.PlatformSchema5], + list[service.PlatformSchema6], ] """ Service registry keyed by reverse-domain name. @@ -227,7 +250,7 @@ class BusinessSchema(Base): """ services: dict[ reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, - list[service.BusinessSchema2], + list[service.BusinessSchema3], ] """ Service registry keyed by reverse-domain name. @@ -348,6 +371,26 @@ class ResponseCatalogSchema(Base): """ +class ResponseLocationSchema(Base): + """ + UCP metadata for location responses. + """ + + model_config = ConfigDict( + extra="allow", + ) + capabilities: ( + dict[ + reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, + list[capability.ResponseSchema], + ] + | None + ) = None + """ + Capability registry keyed by reverse-domain name. + """ + + UcpMetadataUpdateRequest = TypeAliasType( "UcpMetadataUpdateRequest", Annotated[ @@ -356,7 +399,8 @@ class ResponseCatalogSchema(Base): | ResponseCheckoutSchema | ResponseOrderSchema | ResponseCartSchema - | ResponseCatalogSchema, + | ResponseCatalogSchema + | ResponseLocationSchema, Field(..., title="UCP Metadata Update Request"), ], )