From 2a9beb9243f906117d382bc74818f8c86b2a33c0 Mon Sep 17 00:00:00 2001 From: xiaoxuan-hub Date: Wed, 29 Apr 2026 17:37:18 -0700 Subject: [PATCH 1/3] rewrite $ref in top-level oneOf/anyOf branches during variant generation --- preprocess_schemas.py | 7 +++++++ src/ucp_sdk/models/schemas/__init__.py | 1 + src/ucp_sdk/models/schemas/common/__init__.py | 1 + src/ucp_sdk/models/schemas/shopping/__init__.py | 1 + src/ucp_sdk/models/schemas/shopping/types/__init__.py | 1 + .../types/fulfillment_destination_create_request.py | 9 ++++++--- .../types/fulfillment_destination_update_request.py | 9 ++++++--- src/ucp_sdk/models/schemas/transports/__init__.py | 1 + 8 files changed, 24 insertions(+), 6 deletions(-) diff --git a/preprocess_schemas.py b/preprocess_schemas.py index 9294143..50a5954 100644 --- a/preprocess_schemas.py +++ b/preprocess_schemas.py @@ -514,6 +514,13 @@ def _create_single_variant( variant, op, file_path, global_variant_requirements ) + # Rewrite $refs in top-level oneOf/anyOf branches (e.g. fulfillment_destination + # has no properties, only oneOf with external $refs that need variant rewriting). + for poly_key in ["oneOf", "anyOf"]: + if poly_key in variant: + rewrite_refs_to_variants( + variant[poly_key], op, file_path, global_variant_requirements + ) return variant diff --git a/src/ucp_sdk/models/schemas/__init__.py b/src/ucp_sdk/models/schemas/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/schemas/__init__.py +++ b/src/ucp_sdk/models/schemas/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/common/__init__.py b/src/ucp_sdk/models/schemas/common/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/schemas/common/__init__.py +++ b/src/ucp_sdk/models/schemas/common/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/shopping/__init__.py b/src/ucp_sdk/models/schemas/shopping/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/schemas/shopping/__init__.py +++ b/src/ucp_sdk/models/schemas/shopping/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/shopping/types/__init__.py b/src/ucp_sdk/models/schemas/shopping/types/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/__init__.py +++ b/src/ucp_sdk/models/schemas/shopping/types/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + 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 2eeb290..4d7bbb5 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 @@ -23,13 +23,16 @@ from pydantic import Field from typing_extensions import TypeAliasType -from . import retail_location, shipping_destination +from . import ( + retail_location_create_request, + shipping_destination_create_request, +) FulfillmentDestinationCreateRequest = TypeAliasType( "FulfillmentDestinationCreateRequest", Annotated[ - shipping_destination.ShippingDestination - | retail_location.RetailLocation, + shipping_destination_create_request.ShippingDestinationCreateRequest + | retail_location_create_request.RetailLocationCreateRequest, Field(..., title="Fulfillment Destination Create Request"), ], ) 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 e04ace3..add2079 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 @@ -23,13 +23,16 @@ from pydantic import Field from typing_extensions import TypeAliasType -from . import retail_location, shipping_destination +from . import ( + retail_location_update_request, + shipping_destination_update_request, +) FulfillmentDestinationUpdateRequest = TypeAliasType( "FulfillmentDestinationUpdateRequest", Annotated[ - shipping_destination.ShippingDestination - | retail_location.RetailLocation, + shipping_destination_update_request.ShippingDestinationUpdateRequest + | retail_location_update_request.RetailLocationUpdateRequest, Field(..., title="Fulfillment Destination Update Request"), ], ) diff --git a/src/ucp_sdk/models/schemas/transports/__init__.py b/src/ucp_sdk/models/schemas/transports/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/schemas/transports/__init__.py +++ b/src/ucp_sdk/models/schemas/transports/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + From e652462c7dca64fde8a9374fc3552276d1f477c5 Mon Sep 17 00:00:00 2001 From: xiaoxuan-hub Date: Wed, 29 Apr 2026 19:02:13 -0700 Subject: [PATCH 2/3] update --- preprocess_schemas.py | 65 +++++++++++-------- src/ucp_sdk/models/schemas/__init__.py | 1 - src/ucp_sdk/models/schemas/common/__init__.py | 1 - .../models/schemas/shopping/__init__.py | 1 - .../schemas/shopping/cart_create_request.py | 4 +- .../schemas/shopping/cart_update_request.py | 4 +- .../models/schemas/shopping/types/__init__.py | 1 - .../types/error_code_create_request.py | 48 ++++++++++++++ .../types/error_code_update_request.py | 48 ++++++++++++++ .../types/info_code_create_request.py | 44 +++++++++++++ .../types/info_code_update_request.py | 44 +++++++++++++ .../shopping/types/message_create_request.py | 12 ++-- .../types/message_error_create_request.py | 57 ++++++++++++++++ .../types/message_error_update_request.py | 57 ++++++++++++++++ .../types/message_info_create_request.py | 48 ++++++++++++++ .../types/message_info_update_request.py | 48 ++++++++++++++ .../shopping/types/message_update_request.py | 12 ++-- .../types/message_warning_create_request.py | 60 +++++++++++++++++ .../types/message_warning_update_request.py | 60 +++++++++++++++++ .../shipping_destination_create_request.py | 4 +- .../shipping_destination_update_request.py | 4 +- .../types/signed_amount_create_request.py | 32 +++++++++ .../types/signed_amount_update_request.py | 32 +++++++++ .../shopping/types/totals_create_request.py | 7 +- .../shopping/types/totals_update_request.py | 7 +- .../types/warning_code_create_request.py | 44 +++++++++++++ .../types/warning_code_update_request.py | 44 +++++++++++++ .../models/schemas/transports/__init__.py | 1 - .../models/schemas/ucp_create_request.py | 51 ++++++++++----- .../models/schemas/ucp_update_request.py | 51 ++++++++++----- 30 files changed, 807 insertions(+), 85 deletions(-) create mode 100644 src/ucp_sdk/models/schemas/shopping/types/error_code_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/error_code_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/info_code_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/info_code_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/message_error_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/message_error_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/message_info_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/message_info_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/message_warning_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/message_warning_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/signed_amount_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/signed_amount_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/warning_code_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/warning_code_update_request.py diff --git a/preprocess_schemas.py b/preprocess_schemas.py index 50a5954..e72ce29 100644 --- a/preprocess_schemas.py +++ b/preprocess_schemas.py @@ -514,13 +514,12 @@ def _create_single_variant( variant, op, file_path, global_variant_requirements ) - # Rewrite $refs in top-level oneOf/anyOf branches (e.g. fulfillment_destination - # has no properties, only oneOf with external $refs that need variant rewriting). - for poly_key in ["oneOf", "anyOf"]: - if poly_key in variant: - rewrite_refs_to_variants( - variant[poly_key], op, file_path, global_variant_requirements - ) + # Rewrite all external $refs in the variant schema to point to their + # corresponding request variants where applicable. This covers top-level + # oneOf/anyOf/allOf branches as well as array items. + rewrite_refs_to_variants( + variant, op, file_path, global_variant_requirements + ) return variant @@ -577,19 +576,28 @@ def normalize_metadata_schemas(schemas, target_dir): def extract_external_refs(schema, path): - """Finds all relative external file references in the schema properties.""" + """Finds all relative external file references in the schema.""" refs = [] - props = schema.get("properties", {}) - if not isinstance(props, dict): - return refs - for name, data in props.items(): + def _scan(name, data): for node in iter_nodes(data): if isinstance(node, dict) and "$ref" in node: ref = node["$ref"] if "#" not in ref: abs_path = str((path.parent / ref).resolve()) refs.append((name, abs_path)) + + props = schema.get("properties", {}) + if isinstance(props, dict): + for name, data in props.items(): + _scan(name, data) + + # Also scan top-level composition keywords (oneOf, anyOf, allOf, items) + for key in ["oneOf", "anyOf", "allOf"]: + if key in schema: + _scan(key, schema[key]) + if "items" in schema: + _scan("items", schema["items"]) return refs @@ -606,23 +614,28 @@ def propagate_needs_transitive(variant_needs, schema_refs, schemas): continue for op in list(variant_needs[path]): - for prop_name, child_path in refs: + for ref_name, child_path in refs: if child_path not in schemas: continue - # Only propagate if the property isn't 'omit'ted for this op - data = ( - schemas[path].get("properties", {}).get(prop_name, {}) - ) - include, _ = eval_prop_inclusion( - prop_name, data, op, schemas[path].get("required", []) - ) - - if include: - target_set = variant_needs.setdefault(child_path, set()) - if op not in target_set: - target_set.add(op) - changed = True + # For property refs, check if the property is included for this op. + # For non-property refs (oneOf, anyOf, allOf, items), always propagate. + props = schemas[path].get("properties", {}) + if ref_name in props: + data = props[ref_name] + include, _ = eval_prop_inclusion( + ref_name, + data, + op, + schemas[path].get("required", []), + ) + if not include: + continue + + target_set = variant_needs.setdefault(child_path, set()) + if op not in target_set: + target_set.add(op) + changed = True # --- Main Flow --- diff --git a/src/ucp_sdk/models/schemas/__init__.py b/src/ucp_sdk/models/schemas/__init__.py index 421dc21..1252d6b 100644 --- a/src/ucp_sdk/models/schemas/__init__.py +++ b/src/ucp_sdk/models/schemas/__init__.py @@ -15,4 +15,3 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable - diff --git a/src/ucp_sdk/models/schemas/common/__init__.py b/src/ucp_sdk/models/schemas/common/__init__.py index 421dc21..1252d6b 100644 --- a/src/ucp_sdk/models/schemas/common/__init__.py +++ b/src/ucp_sdk/models/schemas/common/__init__.py @@ -15,4 +15,3 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable - diff --git a/src/ucp_sdk/models/schemas/shopping/__init__.py b/src/ucp_sdk/models/schemas/shopping/__init__.py index 421dc21..1252d6b 100644 --- a/src/ucp_sdk/models/schemas/shopping/__init__.py +++ b/src/ucp_sdk/models/schemas/shopping/__init__.py @@ -15,4 +15,3 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable - 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 636e8aa..d7a93f6 100644 --- a/src/ucp_sdk/models/schemas/shopping/cart_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/cart_create_request.py @@ -20,7 +20,7 @@ from pydantic import BaseModel, ConfigDict -from .checkout import Checkout as Checkout_1 +from .checkout_create_request import CheckoutCreateRequest from .types import ( attribution_create_request, buyer_create_request, @@ -56,7 +56,7 @@ class CartCreateRequest(BaseModel): """ -class Checkout(Checkout_1): +class Checkout(CheckoutCreateRequest): """ Checkout extended with cart capability. Adds cart_id to create_checkout for cart-to-checkout conversion. """ 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 c4d35f9..7c523ac 100644 --- a/src/ucp_sdk/models/schemas/shopping/cart_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/cart_update_request.py @@ -20,7 +20,7 @@ from pydantic import BaseModel, ConfigDict -from .checkout import Checkout as Checkout_1 +from .checkout_update_request import CheckoutUpdateRequest from .types import ( attribution_update_request, buyer_update_request, @@ -60,7 +60,7 @@ class CartUpdateRequest(BaseModel): """ -class Checkout(Checkout_1): +class Checkout(CheckoutUpdateRequest): """ Checkout extended with cart capability. Adds cart_id to create_checkout for cart-to-checkout conversion. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/__init__.py b/src/ucp_sdk/models/schemas/shopping/types/__init__.py index 421dc21..1252d6b 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/__init__.py +++ b/src/ucp_sdk/models/schemas/shopping/types/__init__.py @@ -15,4 +15,3 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable - diff --git a/src/ucp_sdk/models/schemas/shopping/types/error_code_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/error_code_create_request.py new file mode 100644 index 0000000..c70c8e7 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/error_code_create_request.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 Field +from typing_extensions import TypeAliasType + +ErrorCodeCreateRequest = TypeAliasType( + "ErrorCodeCreateRequest", + Annotated[ + str, + Field( + ..., + examples=[ + "not_found", + "out_of_stock", + "item_unavailable", + "address_undeliverable", + "payment_failed", + "eligibility_invalid", + "identity_required", + "insufficient_scope", + ], + title="Error Code Create Request", + ), + ], +) +""" +Error code identifying the type of error. Standard errors are defined in specification (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/shopping/types/error_code_update_request.py new file mode 100644 index 0000000..fab61ba --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/error_code_update_request.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 Field +from typing_extensions import TypeAliasType + +ErrorCodeUpdateRequest = TypeAliasType( + "ErrorCodeUpdateRequest", + Annotated[ + str, + Field( + ..., + examples=[ + "not_found", + "out_of_stock", + "item_unavailable", + "address_undeliverable", + "payment_failed", + "eligibility_invalid", + "identity_required", + "insufficient_scope", + ], + title="Error Code Update Request", + ), + ], +) +""" +Error code identifying the type of error. Standard errors are defined in specification (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/shopping/types/info_code_create_request.py new file mode 100644 index 0000000..5a065eb --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/info_code_create_request.py @@ -0,0 +1,44 @@ +# 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 + +InfoCodeCreateRequest = TypeAliasType( + "InfoCodeCreateRequest", + Annotated[ + str, + Field( + ..., + examples=[ + "identity_optional", + "signal", + "free_shipping", + "not_found", + ], + title="Info Code Create Request", + ), + ], +) +""" +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. +""" diff --git a/src/ucp_sdk/models/schemas/shopping/types/info_code_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/info_code_update_request.py new file mode 100644 index 0000000..ff78415 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/info_code_update_request.py @@ -0,0 +1,44 @@ +# 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 + +InfoCodeUpdateRequest = TypeAliasType( + "InfoCodeUpdateRequest", + Annotated[ + str, + Field( + ..., + examples=[ + "identity_optional", + "signal", + "free_shipping", + "not_found", + ], + title="Info Code Update Request", + ), + ], +) +""" +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. +""" diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/message_create_request.py index 3fa159b..123e75d 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/message_create_request.py @@ -23,14 +23,18 @@ from pydantic import Field from typing_extensions import TypeAliasType -from . import message_error, message_info, message_warning +from . import ( + message_error_create_request, + message_info_create_request, + message_warning_create_request, +) MessageCreateRequest = TypeAliasType( "MessageCreateRequest", Annotated[ - message_error.MessageError - | message_warning.MessageWarning - | message_info.MessageInfo, + message_error_create_request.MessageErrorCreateRequest + | message_warning_create_request.MessageWarningCreateRequest + | message_info_create_request.MessageInfoCreateRequest, Field(..., title="Message Create Request"), ], ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_error_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/message_error_create_request.py new file mode 100644 index 0000000..9375f6c --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/message_error_create_request.py @@ -0,0 +1,57 @@ +# 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 BaseModel, ConfigDict + +from . import error_code_create_request + + +class MessageErrorCreateRequest(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + type: Literal["error"] + """ + Message type discriminator. + """ + code: error_code_create_request.ErrorCodeCreateRequest + path: str | None = None + """ + RFC 9535 JSONPath to the component the message refers to (e.g., $.items[1]). + """ + content_type: Literal["plain", "markdown"] | None = "plain" + """ + Content format, default = plain. + """ + content: str + """ + Human-readable message. + """ + severity: Literal[ + "recoverable", + "requires_buyer_input", + "requires_buyer_review", + "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'. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_error_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/message_error_update_request.py new file mode 100644 index 0000000..3ad566d --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/message_error_update_request.py @@ -0,0 +1,57 @@ +# 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 BaseModel, ConfigDict + +from . import error_code_update_request + + +class MessageErrorUpdateRequest(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + type: Literal["error"] + """ + Message type discriminator. + """ + code: error_code_update_request.ErrorCodeUpdateRequest + path: str | None = None + """ + RFC 9535 JSONPath to the component the message refers to (e.g., $.items[1]). + """ + content_type: Literal["plain", "markdown"] | None = "plain" + """ + Content format, default = plain. + """ + content: str + """ + Human-readable message. + """ + severity: Literal[ + "recoverable", + "requires_buyer_input", + "requires_buyer_review", + "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'. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_info_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/message_info_create_request.py new file mode 100644 index 0000000..aa715d7 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/message_info_create_request.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 Literal + +from pydantic import BaseModel, ConfigDict + +from . import info_code_create_request + + +class MessageInfoCreateRequest(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + type: Literal["info"] + """ + Message type discriminator. + """ + path: str | None = None + """ + RFC 9535 JSONPath to the component the message refers to. + """ + code: info_code_create_request.InfoCodeCreateRequest | None = None + content_type: Literal["plain", "markdown"] | None = "plain" + """ + Content format, default = plain. + """ + content: str + """ + Human-readable message. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_info_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/message_info_update_request.py new file mode 100644 index 0000000..5f206ad --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/message_info_update_request.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 Literal + +from pydantic import BaseModel, ConfigDict + +from . import info_code_update_request + + +class MessageInfoUpdateRequest(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + type: Literal["info"] + """ + Message type discriminator. + """ + path: str | None = None + """ + RFC 9535 JSONPath to the component the message refers to. + """ + code: info_code_update_request.InfoCodeUpdateRequest | None = None + content_type: Literal["plain", "markdown"] | None = "plain" + """ + Content format, default = plain. + """ + content: str + """ + Human-readable message. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/message_update_request.py index 7a86d1f..06e63e4 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/message_update_request.py @@ -23,14 +23,18 @@ from pydantic import Field from typing_extensions import TypeAliasType -from . import message_error, message_info, message_warning +from . import ( + message_error_update_request, + message_info_update_request, + message_warning_update_request, +) MessageUpdateRequest = TypeAliasType( "MessageUpdateRequest", Annotated[ - message_error.MessageError - | message_warning.MessageWarning - | message_info.MessageInfo, + message_error_update_request.MessageErrorUpdateRequest + | message_warning_update_request.MessageWarningUpdateRequest + | message_info_update_request.MessageInfoUpdateRequest, Field(..., title="Message Update Request"), ], ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_warning_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/message_warning_create_request.py new file mode 100644 index 0000000..5294a25 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/message_warning_create_request.py @@ -0,0 +1,60 @@ +# 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 AnyUrl, BaseModel, ConfigDict + +from . import warning_code_create_request + + +class MessageWarningCreateRequest(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + type: Literal["warning"] + """ + Message type discriminator. + """ + path: str | None = None + """ + JSONPath (RFC 9535) to related field (e.g., $.line_items[0]). + """ + code: warning_code_create_request.WarningCodeCreateRequest + content: str + """ + Human-readable warning message that MUST be displayed. + """ + content_type: Literal["plain", "markdown"] | None = "plain" + """ + Content format, default = plain. + """ + presentation: str | None = "notice" + """ + Rendering contract for this warning. 'notice' (default): platform MUST display, MAY dismiss. 'disclosure': platform MUST display in proximity to the path-referenced component, MUST NOT hide or auto-dismiss. See specification for full contract. + """ + image_url: AnyUrl | None = None + """ + URL to a required visual element (e.g., warning symbol, energy class label). + """ + url: AnyUrl | None = None + """ + Reference URL for more information (e.g., regulatory site, registry entry, policy page). + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_warning_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/message_warning_update_request.py new file mode 100644 index 0000000..44cd08d --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/message_warning_update_request.py @@ -0,0 +1,60 @@ +# 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 AnyUrl, BaseModel, ConfigDict + +from . import warning_code_update_request + + +class MessageWarningUpdateRequest(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + type: Literal["warning"] + """ + Message type discriminator. + """ + path: str | None = None + """ + JSONPath (RFC 9535) to related field (e.g., $.line_items[0]). + """ + code: warning_code_update_request.WarningCodeUpdateRequest + content: str + """ + Human-readable warning message that MUST be displayed. + """ + content_type: Literal["plain", "markdown"] | None = "plain" + """ + Content format, default = plain. + """ + presentation: str | None = "notice" + """ + Rendering contract for this warning. 'notice' (default): platform MUST display, MAY dismiss. 'disclosure': platform MUST display in proximity to the path-referenced component, MUST NOT hide or auto-dismiss. See specification for full contract. + """ + image_url: AnyUrl | None = None + """ + URL to a required visual element (e.g., warning symbol, energy class label). + """ + url: AnyUrl | None = None + """ + Reference URL for more information (e.g., regulatory site, registry entry, policy page). + """ 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 46cfc64..b454152 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 @@ -20,10 +20,10 @@ from pydantic import ConfigDict -from .postal_address import PostalAddress +from .postal_address_create_request import PostalAddressCreateRequest -class ShippingDestinationCreateRequest(PostalAddress): +class ShippingDestinationCreateRequest(PostalAddressCreateRequest): """ Shipping destination. """ 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 fea30e9..3ab1733 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 @@ -20,10 +20,10 @@ from pydantic import ConfigDict -from .postal_address import PostalAddress +from .postal_address_update_request import PostalAddressUpdateRequest -class ShippingDestinationUpdateRequest(PostalAddress): +class ShippingDestinationUpdateRequest(PostalAddressUpdateRequest): """ Shipping destination. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/signed_amount_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/signed_amount_create_request.py new file mode 100644 index 0000000..436d0f2 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/signed_amount_create_request.py @@ -0,0 +1,32 @@ +# 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 + +SignedAmountCreateRequest = TypeAliasType( + "SignedAmountCreateRequest", + Annotated[int, Field(..., 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/shopping/types/signed_amount_update_request.py new file mode 100644 index 0000000..985bd10 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/signed_amount_update_request.py @@ -0,0 +1,32 @@ +# 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 + +SignedAmountUpdateRequest = TypeAliasType( + "SignedAmountUpdateRequest", + Annotated[int, Field(..., 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/shopping/types/totals_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/totals_create_request.py index c201fdb..eaa5dc0 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/totals_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/totals_create_request.py @@ -23,11 +23,14 @@ from pydantic import Field from typing_extensions import TypeAliasType -from . import total +from . import total_create_request TotalsCreateRequest = TypeAliasType( "TotalsCreateRequest", - Annotated[list[total.Total], Field(..., title="Totals Create Request")], + Annotated[ + list[total_create_request.TotalCreateRequest], + Field(..., title="Totals Create Request"), + ], ) """ Pricing breakdown provided by the business. MUST contain exactly one subtotal and one total entry. Detail types (tax, fee, discount, fulfillment) may appear multiple times for itemization. Platforms MUST render all entries in order using display_text and amount. diff --git a/src/ucp_sdk/models/schemas/shopping/types/totals_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/totals_update_request.py index 1bb995d..c032a8b 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/totals_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/totals_update_request.py @@ -23,11 +23,14 @@ from pydantic import Field from typing_extensions import TypeAliasType -from . import total +from . import total_update_request TotalsUpdateRequest = TypeAliasType( "TotalsUpdateRequest", - Annotated[list[total.Total], Field(..., title="Totals Update Request")], + Annotated[ + list[total_update_request.TotalUpdateRequest], + Field(..., title="Totals Update Request"), + ], ) """ Pricing breakdown provided by the business. MUST contain exactly one subtotal and one total entry. Detail types (tax, fee, discount, fulfillment) may appear multiple times for itemization. Platforms MUST render all entries in order using display_text and amount. diff --git a/src/ucp_sdk/models/schemas/shopping/types/warning_code_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/warning_code_create_request.py new file mode 100644 index 0000000..e6dd78d --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/warning_code_create_request.py @@ -0,0 +1,44 @@ +# 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 + +WarningCodeCreateRequest = TypeAliasType( + "WarningCodeCreateRequest", + Annotated[ + str, + Field( + ..., + examples=[ + "final_sale", + "prop65", + "fulfillment_changed", + "age_restricted", + ], + title="Warning Code Create Request", + ), + ], +) +""" +Warning code identifying the type of warning. Standard codes are defined in capability specs (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/shopping/types/warning_code_update_request.py new file mode 100644 index 0000000..0ef6133 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/warning_code_update_request.py @@ -0,0 +1,44 @@ +# 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 + +WarningCodeUpdateRequest = TypeAliasType( + "WarningCodeUpdateRequest", + Annotated[ + str, + Field( + ..., + examples=[ + "final_sale", + "prop65", + "fulfillment_changed", + "age_restricted", + ], + title="Warning Code Update Request", + ), + ], +) +""" +Warning code identifying the type of warning. Standard codes are defined in capability specs (see examples), and have standardized semantics; freeform codes are permitted. +""" diff --git a/src/ucp_sdk/models/schemas/transports/__init__.py b/src/ucp_sdk/models/schemas/transports/__init__.py index 421dc21..1252d6b 100644 --- a/src/ucp_sdk/models/schemas/transports/__init__.py +++ b/src/ucp_sdk/models/schemas/transports/__init__.py @@ -15,4 +15,3 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable - diff --git a/src/ucp_sdk/models/schemas/ucp_create_request.py b/src/ucp_sdk/models/schemas/ucp_create_request.py index 5f68cf8..55219f8 100644 --- a/src/ucp_sdk/models/schemas/ucp_create_request.py +++ b/src/ucp_sdk/models/schemas/ucp_create_request.py @@ -24,7 +24,7 @@ from typing_extensions import TypeAliasType from . import capability, payment_handler, service -from .shopping.types import reverse_domain_name +from .shopping.types import reverse_domain_name_create_request Version = TypeAliasType( "Version", Annotated[str, Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$")] @@ -65,7 +65,11 @@ class Requires(BaseModel): Required protocol version. """ capabilities: ( - dict[reverse_domain_name.ReverseDomainName, VersionConstraint] | None + dict[ + reverse_domain_name_create_request.ReverseDomainNameCreateRequest, + VersionConstraint, + ] + | None ) = None """ Required capability versions, keyed by capability name. Keys must be a subset of the extension's $defs keys. @@ -116,20 +120,30 @@ class Base(BaseModel): Application-level status of the UCP operation. """ services: ( - dict[reverse_domain_name.ReverseDomainName, list[service.Base]] | None + dict[ + reverse_domain_name_create_request.ReverseDomainNameCreateRequest, + list[service.Base], + ] + | None ) = None """ Service registry keyed by reverse-domain name. """ capabilities: ( - dict[reverse_domain_name.ReverseDomainName, list[capability.Base]] + dict[ + reverse_domain_name_create_request.ReverseDomainNameCreateRequest, + list[capability.Base], + ] | None ) = None """ Capability registry keyed by reverse-domain name. """ payment_handlers: ( - dict[reverse_domain_name.ReverseDomainName, list[payment_handler.Base]] + dict[ + reverse_domain_name_create_request.ReverseDomainNameCreateRequest, + list[payment_handler.Base], + ] | None ) = None """ @@ -174,14 +188,15 @@ class PlatformSchema(Base): extra="allow", ) services: dict[ - reverse_domain_name.ReverseDomainName, list[service.PlatformSchema5] + reverse_domain_name_create_request.ReverseDomainNameCreateRequest, + list[service.PlatformSchema5], ] """ Service registry keyed by reverse-domain name. """ capabilities: ( dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_create_request.ReverseDomainNameCreateRequest, list[capability.PlatformSchema], ] | None @@ -190,7 +205,7 @@ class PlatformSchema(Base): Capability registry keyed by reverse-domain name. """ payment_handlers: dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_create_request.ReverseDomainNameCreateRequest, list[payment_handler.PlatformSchema], ] """ @@ -211,14 +226,15 @@ 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_create_request.ReverseDomainNameCreateRequest, + list[service.BusinessSchema2], ] """ Service registry keyed by reverse-domain name. """ capabilities: ( dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_create_request.ReverseDomainNameCreateRequest, list[capability.BusinessSchema], ] | None @@ -227,7 +243,7 @@ class BusinessSchema(Base): Capability registry keyed by reverse-domain name. """ payment_handlers: dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_create_request.ReverseDomainNameCreateRequest, list[payment_handler.BusinessSchema], ] """ @@ -245,7 +261,8 @@ class ResponseCheckoutSchema(Base): ) services: ( dict[ - reverse_domain_name.ReverseDomainName, list[service.ResponseSchema2] + reverse_domain_name_create_request.ReverseDomainNameCreateRequest, + list[service.ResponseSchema2], ] | None ) = None @@ -254,7 +271,7 @@ class ResponseCheckoutSchema(Base): """ capabilities: ( dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_create_request.ReverseDomainNameCreateRequest, list[capability.ResponseSchema], ] | None @@ -263,7 +280,7 @@ class ResponseCheckoutSchema(Base): Capability registry keyed by reverse-domain name. """ payment_handlers: dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_create_request.ReverseDomainNameCreateRequest, list[payment_handler.ResponseSchema], ] """ @@ -281,7 +298,7 @@ class ResponseOrderSchema(Base): ) capabilities: ( dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_create_request.ReverseDomainNameCreateRequest, list[capability.ResponseSchema], ] | None @@ -301,7 +318,7 @@ class ResponseCartSchema(Base): ) capabilities: ( dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_create_request.ReverseDomainNameCreateRequest, list[capability.ResponseSchema], ] | None @@ -321,7 +338,7 @@ class ResponseCatalogSchema(Base): ) capabilities: ( dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_create_request.ReverseDomainNameCreateRequest, list[capability.ResponseSchema], ] | None diff --git a/src/ucp_sdk/models/schemas/ucp_update_request.py b/src/ucp_sdk/models/schemas/ucp_update_request.py index 57cf54c..0ea27e3 100644 --- a/src/ucp_sdk/models/schemas/ucp_update_request.py +++ b/src/ucp_sdk/models/schemas/ucp_update_request.py @@ -24,7 +24,7 @@ from typing_extensions import TypeAliasType from . import capability, payment_handler, service -from .shopping.types import reverse_domain_name +from .shopping.types import reverse_domain_name_update_request Version = TypeAliasType( "Version", Annotated[str, Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$")] @@ -65,7 +65,11 @@ class Requires(BaseModel): Required protocol version. """ capabilities: ( - dict[reverse_domain_name.ReverseDomainName, VersionConstraint] | None + dict[ + reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, + VersionConstraint, + ] + | None ) = None """ Required capability versions, keyed by capability name. Keys must be a subset of the extension's $defs keys. @@ -116,20 +120,30 @@ class Base(BaseModel): Application-level status of the UCP operation. """ services: ( - dict[reverse_domain_name.ReverseDomainName, list[service.Base]] | None + dict[ + reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, + list[service.Base], + ] + | None ) = None """ Service registry keyed by reverse-domain name. """ capabilities: ( - dict[reverse_domain_name.ReverseDomainName, list[capability.Base]] + dict[ + reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, + list[capability.Base], + ] | None ) = None """ Capability registry keyed by reverse-domain name. """ payment_handlers: ( - dict[reverse_domain_name.ReverseDomainName, list[payment_handler.Base]] + dict[ + reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, + list[payment_handler.Base], + ] | None ) = None """ @@ -174,14 +188,15 @@ class PlatformSchema(Base): extra="allow", ) services: dict[ - reverse_domain_name.ReverseDomainName, list[service.PlatformSchema5] + reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, + list[service.PlatformSchema5], ] """ Service registry keyed by reverse-domain name. """ capabilities: ( dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, list[capability.PlatformSchema], ] | None @@ -190,7 +205,7 @@ class PlatformSchema(Base): Capability registry keyed by reverse-domain name. """ payment_handlers: dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, list[payment_handler.PlatformSchema], ] """ @@ -211,14 +226,15 @@ 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_update_request.ReverseDomainNameUpdateRequest, + list[service.BusinessSchema2], ] """ Service registry keyed by reverse-domain name. """ capabilities: ( dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, list[capability.BusinessSchema], ] | None @@ -227,7 +243,7 @@ class BusinessSchema(Base): Capability registry keyed by reverse-domain name. """ payment_handlers: dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, list[payment_handler.BusinessSchema], ] """ @@ -245,7 +261,8 @@ class ResponseCheckoutSchema(Base): ) services: ( dict[ - reverse_domain_name.ReverseDomainName, list[service.ResponseSchema2] + reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, + list[service.ResponseSchema2], ] | None ) = None @@ -254,7 +271,7 @@ class ResponseCheckoutSchema(Base): """ capabilities: ( dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, list[capability.ResponseSchema], ] | None @@ -263,7 +280,7 @@ class ResponseCheckoutSchema(Base): Capability registry keyed by reverse-domain name. """ payment_handlers: dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, list[payment_handler.ResponseSchema], ] """ @@ -281,7 +298,7 @@ class ResponseOrderSchema(Base): ) capabilities: ( dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, list[capability.ResponseSchema], ] | None @@ -301,7 +318,7 @@ class ResponseCartSchema(Base): ) capabilities: ( dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, list[capability.ResponseSchema], ] | None @@ -321,7 +338,7 @@ class ResponseCatalogSchema(Base): ) capabilities: ( dict[ - reverse_domain_name.ReverseDomainName, + reverse_domain_name_update_request.ReverseDomainNameUpdateRequest, list[capability.ResponseSchema], ] | None From 34c310754e4678e3b1aa3951bfcbe95bba2a744c Mon Sep 17 00:00:00 2001 From: damaz91 Date: Fri, 17 Jul 2026 08:26:17 +0000 Subject: [PATCH 3/3] test: add coverage for composition variant propagation and rewriting --- tests/test_codegen_pipeline.py | 63 ++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/test_codegen_pipeline.py b/tests/test_codegen_pipeline.py index 4890720..00a003c 100644 --- a/tests/test_codegen_pipeline.py +++ b/tests/test_codegen_pipeline.py @@ -347,6 +347,49 @@ def test_array_variant_preserves_root_and_filters_nested_objects( self.assertEqual(set(item_schema["required"]), {"amount", "label"}) self.assertEqual(variant["title"], "Totals Create Request") + def test_composition_variant_rewrites_refs(self) -> None: + """Composition variants (oneOf/anyOf/allOf) rewrite refs to variants.""" + schema = { + "$id": "https://ucp.dev/schemas/poly.json", + "title": "Poly", + "oneOf": [{"$ref": "child_a.json"}, {"$ref": "child_b.json"}], + "allOf": [{"$ref": "parent.json"}], + "anyOf": [{"$ref": "other.json"}], + } + file_path = Path("/schemas/poly.json") + child_a_path = str((file_path.parent / "child_a.json").resolve()) + child_b_path = str((file_path.parent / "child_b.json").resolve()) + parent_path = str((file_path.parent / "parent.json").resolve()) + other_path = str((file_path.parent / "other.json").resolve()) + + variant_needs = { + child_a_path: {"create"}, + child_b_path: {"create"}, + parent_path: {"create"}, + other_path: {"create"}, + } + + variant = preprocess_schemas._create_single_variant( + schema, + "create", + "poly", + file_path, + variant_needs, + ) + + self.assertEqual( + variant["oneOf"][0]["$ref"], "child_a_create_request.json" + ) + self.assertEqual( + variant["oneOf"][1]["$ref"], "child_b_create_request.json" + ) + self.assertEqual( + variant["allOf"][0]["$ref"], "parent_create_request.json" + ) + self.assertEqual( + variant["anyOf"][0]["$ref"], "other_create_request.json" + ) + def test_generate_variants_writes_operation_specific_files(self) -> None: """Variant generation writes one filtered file per operation.""" schema = { @@ -473,6 +516,26 @@ def test_variant_needs_propagate_transitively_and_respect_omit( self.assertEqual(variant_needs[child_path], {"create"}) self.assertEqual(variant_needs[grandchild_path], {"create"}) + def test_variant_needs_propagate_through_composition_keywords(self) -> None: + """Variant dependencies propagate unconditionally through oneOf/anyOf/allOf/items.""" + parent_path = "/schemas/parent.json" + child_path = "/schemas/child.json" + schemas = { + parent_path: {"oneOf": [{"$ref": "child.json"}]}, + child_path: {"properties": {}}, + } + schema_refs = { + parent_path: [("oneOf", child_path)], + child_path: [], + } + variant_needs = {parent_path: {"create", "update"}} + + preprocess_schemas.propagate_needs_transitive( + variant_needs, schema_refs, schemas + ) + + self.assertEqual(variant_needs[child_path], {"create", "update"}) + def test_main_preprocesses_schema_tree_end_to_end(self) -> None: """The full pipeline normalizes schemas and writes linked variants.""" with tempfile.TemporaryDirectory() as temp_dir: