From 452d10987bfe2ce15b3020b0e79494cf38c81895 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Fri, 6 Feb 2026 10:10:34 +0000 Subject: [PATCH 1/7] update SDK to 2026-01-23 --- generate_models.sh | 41 +++- preprocess_schemas.py | 192 +++++++++++++++ pyproject.toml | 21 +- src/ucp_sdk/__init__.py | 19 -- src/ucp_sdk/models/_internal.py | 227 ------------------ .../models/discovery/profile_schema.py | 114 +++++---- .../models/handlers/tokenization/openapi.py | 3 +- src/ucp_sdk/models/ruff.toml | 16 ++ src/ucp_sdk/models/schemas/capability.py | 36 ++- src/ucp_sdk/models/schemas/payment_handler.py | 55 +++++ src/ucp_sdk/models/schemas/service.py | 197 +++++++++++++++ .../models/schemas/shopping/ap2_mandate.py | 126 ++++------ ...buyer_consent_resp.py => buyer_consent.py} | 59 ++--- .../shopping/buyer_consent_create_req.py | 79 ------ .../shopping/buyer_consent_update_req.py | 79 ------ src/ucp_sdk/models/schemas/shopping/cart.py | 128 ++++++++++ .../models/schemas/shopping/checkout.py | 190 +++++++++++++++ .../schemas/shopping/checkout_create_req.py | 44 ---- .../models/schemas/shopping/checkout_resp.py | 93 ------- .../schemas/shopping/checkout_update_req.py | 48 ---- .../{discount_resp.py => discount.py} | 91 ++++--- .../schemas/shopping/discount_create_req.py | 107 --------- .../schemas/shopping/discount_update_req.py | 107 --------- .../schemas/shopping/fulfillment/__init__.py | 58 +++++ .../shopping/fulfillment/dev}/__init__.py | 0 .../shopping/fulfillment/dev/ucp/__init__.py} | 5 - .../shopping/fulfillment/dev/ucp/shopping.py} | 5 +- .../shopping/fulfillment_create_req.py | 77 ------ .../schemas/shopping/fulfillment_resp.py | 79 ------ .../shopping/fulfillment_update_req.py | 77 ------ src/ucp_sdk/models/schemas/shopping/order.py | 85 +++---- .../{payment_update_req.py => payment.py} | 19 +- .../schemas/shopping/payment_create_req.py | 38 --- .../models/schemas/shopping/payment_data.py | 31 --- .../models/schemas/shopping/payment_resp.py | 42 ---- .../schemas/shopping/types/account_info.py | 14 +- .../schemas/shopping/types/adjustment.py | 55 +++-- .../models/schemas/shopping/types/binding.py | 19 +- .../types/business_fulfillment_config.py | 59 +++++ .../models/schemas/shopping/types/buyer.py | 24 +- .../schemas/shopping/types/card_credential.py | 53 ++-- .../shopping/types/card_payment_instrument.py | 62 +++-- .../models/schemas/shopping/types/context.py | 47 ++++ .../schemas/shopping/types/expectation.py | 52 ++-- .../{fulfillment_resp.py => fulfillment.py} | 26 +- ...esp.py => fulfillment_available_method.py} | 29 ++- .../types/fulfillment_available_method_req.py | 29 --- ...tion_req.py => fulfillment_destination.py} | 19 +- .../types/fulfillment_destination_resp.py | 37 --- .../shopping/types/fulfillment_event.py | 56 ++--- ...ent_group_resp.py => fulfillment_group.py} | 31 +-- .../types/fulfillment_group_create_req.py | 33 --- ...t_method_resp.py => fulfillment_method.py} | 42 ++-- .../types/fulfillment_method_create_req.py | 55 ----- .../types/fulfillment_method_update_req.py | 54 ----- ...t_option_resp.py => fulfillment_option.py} | 43 ++-- .../shopping/types/fulfillment_option_req.py | 29 --- .../schemas/shopping/types/fulfillment_req.py | 36 --- .../shopping/types/{item_resp.py => item.py} | 28 +-- .../schemas/shopping/types/item_create_req.py | 31 --- .../schemas/shopping/types/item_update_req.py | 31 --- .../types/{line_item_resp.py => line_item.py} | 32 +-- .../shopping/types/line_item_create_req.py | 35 --- .../shopping/types/line_item_update_req.py | 40 --- .../models/schemas/shopping/types/link.py | 18 +- .../types/merchant_fulfillment_config.py | 41 ++-- .../models/schemas/shopping/types/message.py | 19 +- .../schemas/shopping/types/message_error.py | 33 ++- .../schemas/shopping/types/message_info.py | 27 ++- .../schemas/shopping/types/message_warning.py | 27 ++- .../shopping/types/order_confirmation.py | 18 +- .../schemas/shopping/types/order_line_item.py | 55 +++-- .../shopping/types/payment_credential.py | 26 +- .../types/payment_handler_create_req.py | 27 --- .../shopping/types/payment_handler_resp.py | 54 ----- .../types/payment_handler_update_req.py | 27 --- .../shopping/types/payment_identity.py | 14 +- .../shopping/types/payment_instrument.py | 51 +++- .../shopping/types/payment_instrument_base.py | 47 ---- .../types/platform_fulfillment_config.py | 14 +- .../schemas/shopping/types/postal_address.py | 44 ++-- ...il_location_resp.py => retail_location.py} | 25 +- ...ination_req.py => shipping_destination.py} | 17 +- .../types/shipping_destination_resp.py | 34 --- ...tial_create_req.py => token_credential.py} | 24 +- .../shopping/types/token_credential_resp.py | 33 --- .../types/token_credential_update_req.py | 37 --- .../types/{total_resp.py => total.py} | 29 +-- .../shopping/types/total_create_req.py | 27 --- .../shopping/types/total_update_req.py | 27 --- .../models/schemas/transports/__init__.py | 18 ++ .../embedded_config.py} | 20 +- src/ucp_sdk/models/schemas/ucp.py | 174 ++++++++++++-- .../shopping/embedded.py} | 18 +- .../{embedded_openrpc.py => openapi.py} | 3 +- .../shopping/{mcp_openrpc.py => openrpc.py} | 3 +- src/ucp_sdk/py.typed | 0 97 files changed, 2020 insertions(+), 2700 deletions(-) create mode 100644 preprocess_schemas.py delete mode 100644 src/ucp_sdk/__init__.py delete mode 100644 src/ucp_sdk/models/_internal.py create mode 100644 src/ucp_sdk/models/ruff.toml create mode 100644 src/ucp_sdk/models/schemas/payment_handler.py create mode 100644 src/ucp_sdk/models/schemas/service.py rename src/ucp_sdk/models/schemas/shopping/{buyer_consent_resp.py => buyer_consent.py} (56%) delete mode 100644 src/ucp_sdk/models/schemas/shopping/buyer_consent_create_req.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/buyer_consent_update_req.py create mode 100644 src/ucp_sdk/models/schemas/shopping/cart.py create mode 100644 src/ucp_sdk/models/schemas/shopping/checkout.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/checkout_create_req.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/checkout_resp.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/checkout_update_req.py rename src/ucp_sdk/models/schemas/shopping/{discount_resp.py => discount.py} (63%) delete mode 100644 src/ucp_sdk/models/schemas/shopping/discount_create_req.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/discount_update_req.py create mode 100644 src/ucp_sdk/models/schemas/shopping/fulfillment/__init__.py rename src/ucp_sdk/models/{ => schemas/shopping/fulfillment/dev}/__init__.py (100%) rename src/ucp_sdk/models/{services/service_schema.py => schemas/shopping/fulfillment/dev/ucp/__init__.py} (80%) rename src/ucp_sdk/models/{services/shopping/rest_openapi.py => schemas/shopping/fulfillment/dev/ucp/shopping.py} (93%) delete mode 100644 src/ucp_sdk/models/schemas/shopping/fulfillment_create_req.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/fulfillment_resp.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/fulfillment_update_req.py rename src/ucp_sdk/models/schemas/shopping/{payment_update_req.py => payment.py} (69%) delete mode 100644 src/ucp_sdk/models/schemas/shopping/payment_create_req.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/payment_data.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/payment_resp.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/business_fulfillment_config.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/context.py rename src/ucp_sdk/models/schemas/shopping/types/{fulfillment_resp.py => fulfillment.py} (65%) rename src/ucp_sdk/models/schemas/shopping/types/{fulfillment_available_method_resp.py => fulfillment_available_method.py} (75%) delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_req.py rename src/ucp_sdk/models/schemas/shopping/types/{fulfillment_destination_req.py => fulfillment_destination.py} (65%) delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_resp.py rename src/ucp_sdk/models/schemas/shopping/types/{fulfillment_group_resp.py => fulfillment_group.py} (70%) delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_create_req.py rename src/ucp_sdk/models/schemas/shopping/types/{fulfillment_method_resp.py => fulfillment_method.py} (66%) delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_create_req.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_req.py rename src/ucp_sdk/models/schemas/shopping/types/{fulfillment_option_resp.py => fulfillment_option.py} (68%) delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_req.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_req.py rename src/ucp_sdk/models/schemas/shopping/types/{item_resp.py => item.py} (69%) delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/item_create_req.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/item_update_req.py rename src/ucp_sdk/models/schemas/shopping/types/{line_item_resp.py => line_item.py} (70%) delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/line_item_create_req.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/line_item_update_req.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/payment_handler_create_req.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/payment_handler_resp.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/payment_handler_update_req.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/payment_instrument_base.py rename src/ucp_sdk/models/schemas/shopping/types/{retail_location_resp.py => retail_location.py} (76%) rename src/ucp_sdk/models/schemas/shopping/types/{shipping_destination_req.py => shipping_destination.py} (83%) delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/shipping_destination_resp.py rename src/ucp_sdk/models/schemas/shopping/types/{token_credential_create_req.py => token_credential.py} (67%) delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/token_credential_resp.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/token_credential_update_req.py rename src/ucp_sdk/models/schemas/shopping/types/{total_resp.py => total.py} (78%) delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/total_create_req.py delete mode 100644 src/ucp_sdk/models/schemas/shopping/types/total_update_req.py create mode 100644 src/ucp_sdk/models/schemas/transports/__init__.py rename src/ucp_sdk/models/schemas/{shopping/types/fulfillment_group_update_req.py => transports/embedded_config.py} (60%) rename src/ucp_sdk/models/{schemas/shopping/types/retail_location_req.py => services/shopping/embedded.py} (62%) rename src/ucp_sdk/models/services/shopping/{embedded_openrpc.py => openapi.py} (98%) rename src/ucp_sdk/models/services/shopping/{mcp_openrpc.py => openrpc.py} (98%) delete mode 100644 src/ucp_sdk/py.typed diff --git a/generate_models.sh b/generate_models.sh index 7cebf5a..7826da2 100755 --- a/generate_models.sh +++ b/generate_models.sh @@ -8,9 +8,13 @@ cd "$(dirname "$0")" OUTPUT_DIR="src/ucp_sdk/models" # Schema directory (relative to this script) -SCHEMA_DIR="../../spec/" +SCHEMA_DIR="ucp/source" +TEMP_SCHEMA_DIR="temp_schemas" -echo "Generating Pydantic models from $SCHEMA_DIR..." +echo "Preprocessing schemas..." +python3 preprocess_schemas.py + +echo "Generating Pydantic models from preprocessed schemas..." # Check if uv is installed if ! command -v uv &> /dev/null; then @@ -23,14 +27,35 @@ fi rm -r -f "$OUTPUT_DIR" mkdir -p "$OUTPUT_DIR" +# Create ruff configuration for generated code +cat > "$OUTPUT_DIR/ruff.toml" << 'EOF' +# Ruff configuration for generated models +# These are auto-generated files, so we're more lenient with style rules + +line-length = 120 +target-version = "py311" + +[lint] +select = ["E", "F", "I"] +ignore = ["E501"] + +[lint.pydocstyle] +convention = "google" + +[lint.per-file-ignores] +"__init__.py" = ["D104"] +"*.py" = ["D100", "D101", "D102", "D103", "D200", "D205", "D212"] +EOF + # Run generation using uv # We use --use-schema-description to use descriptions from JSON schema as docstrings # We use --field-constraints to include validation constraints (regex, min/max, etc.) +# Note: Formatters removed as they can hang on large schemas uv run \ --link-mode=copy \ --extra-index-url https://pypi.org/simple python \ -m datamodel_code_generator \ - --input "$SCHEMA_DIR" \ + --input "$TEMP_SCHEMA_DIR" \ --input-file-type jsonschema \ --output "$OUTPUT_DIR" \ --output-model-type pydantic_v2.BaseModel \ @@ -41,7 +66,13 @@ uv run \ --disable-timestamp \ --use-double-quotes \ --no-use-annotated \ - --allow-extra-fields \ - --formatters ruff-format ruff-check + --allow-extra-fields + +echo "Formatting generated models..." +uv run ruff format "$OUTPUT_DIR" +uv run ruff check --fix --config "$OUTPUT_DIR/ruff.toml" "$OUTPUT_DIR" 2>&1 | grep -E "^(All checks passed|Fixed|Found)" || echo "Formatting complete" + +# Clean up temp schemas +rm -rf "$TEMP_SCHEMA_DIR" echo "Done. Models generated in $OUTPUT_DIR" diff --git a/preprocess_schemas.py b/preprocess_schemas.py new file mode 100644 index 0000000..a83004f --- /dev/null +++ b/preprocess_schemas.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 + +# 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. + +""" +Preprocess JSON schemas for datamodel-code-generator compatibility. +""" +import json +import shutil +import copy +from pathlib import Path +from typing import Any, Dict + + +def remove_extension_defs(schema: Dict[str, Any]) -> Dict[str, Any]: + """ + Remove $defs that extend EXTERNAL schemas using allOf. + These cause circular dependency issues. + """ + if "$defs" not in schema: + return schema + + defs_to_remove = [] + + for def_name, def_schema in schema["$defs"].items(): + if isinstance(def_schema, dict) and "allOf" in def_schema: + # Check if this extends an external schema + all_of = def_schema["allOf"] + has_external_ref = False + for item in all_of: + if isinstance(item, dict) and "$ref" in item: + ref = item["$ref"] + # If referencing another top-level schema (not within same file's $defs) + if not ref.startswith("#/"): + has_external_ref = True + break + + if has_external_ref: + print(f" -> Removing extension def: {def_name}") + defs_to_remove.append(def_name) + + # Remove extension defs + for def_name in defs_to_remove: + del schema["$defs"][def_name] + + # Remove empty $defs + if "$defs" in schema and not schema["$defs"]: + del schema["$defs"] + + return schema + + +def inline_internal_refs(obj: Any, defs: Dict[str, Any], processed: set = None) -> Any: + """ + Recursively inline $ref references that point to #/$defs/... + This resolves internal references to avoid cross-file confusion. + """ + if processed is None: + processed = set() + + if isinstance(obj, dict): + # Check for $ref + if "$ref" in obj and len(obj) == 1: + ref = obj["$ref"] + if ref.startswith("#/$defs/"): + def_name = ref.split("/")[-1] + # Avoid infinite recursion + if def_name not in processed and def_name in defs: + processed.add(def_name) + # Inline the definition + inlined = copy.deepcopy(defs[def_name]) + result = inline_internal_refs(inlined, defs, processed) + processed.remove(def_name) + return result + return obj + + # Recursively process all properties + result = {} + for key, value in obj.items(): + result[key] = inline_internal_refs(value, defs, processed) + return result + elif isinstance(obj, list): + return [inline_internal_refs(item, defs, processed) for item in obj] + return obj + + +def flatten_allof_in_defs(schema: Dict[str, Any]) -> Dict[str, Any]: + """ + Flatten allOf patterns within $defs that only use internal references. + """ + if "$defs" not in schema: + return schema + + defs = schema["$defs"] + + for def_name, def_schema in list(defs.items()): + if isinstance(def_schema, dict) and "allOf" in def_schema: + all_of = def_schema["allOf"] + + # Check if all refs are internal + all_internal = True + for item in all_of: + if isinstance(item, dict) and "$ref" in item: + ref = item["$ref"] + if not ref.startswith("#/"): + all_internal = False + break + + if all_internal: + # Flatten the allOf by inlining refs + merged = {} + for item in all_of: + resolved = inline_internal_refs(item, defs, set()) + # Merge properties + for k, v in resolved.items(): + if k == "properties" and k in merged: + merged[k].update(v) + elif k == "required" and k in merged: + merged[k] = list(set(merged[k] + v)) + elif k not in ["title", "description", "allOf"]: + merged[k] = v + + # Keep original title and description + if "title" in def_schema: + merged["title"] = def_schema["title"] + if "description" in def_schema: + merged["description"] = def_schema["description"] + + defs[def_name] = merged + + return schema + + +def preprocess_schema_file(input_path: Path, output_path: Path) -> None: + """Preprocess a single schema file.""" + with open(input_path, 'r', encoding='utf-8') as f: + schema = json.load(f) + + # Remove extension definitions that reference external schemas + schema = remove_extension_defs(schema) + + # Flatten allOf patterns within $defs that only use internal refs + schema = flatten_allof_in_defs(schema) + + # Ensure output directory exists + output_path.parent.mkdir(parents=True, exist_ok=True) + + # Write preprocessed schema + with open(output_path, 'w', encoding='utf-8') as f: + json.dump(schema, f, indent=2) + + +def preprocess_schemas(input_dir: Path, output_dir: Path) -> None: + """Preprocess all schema files in the directory tree.""" + # Clean output directory + if output_dir.exists(): + shutil.rmtree(output_dir) + output_dir.mkdir(parents=True) + + # Find all JSON files + json_files = list(input_dir.rglob("*.json")) + + print(f"Preprocessing {len(json_files)} schema files...") + + for json_file in json_files: + # Calculate relative path + rel_path = json_file.relative_to(input_dir) + output_path = output_dir / rel_path + + print(f" Processing: {rel_path}") + preprocess_schema_file(json_file, output_path) + + print(f"Preprocessing complete. Output in {output_dir}") + + +if __name__ == "__main__": + script_dir = Path(__file__).parent + input_schemas = script_dir / "ucp" / "source" + output_schemas = script_dir / "temp_schemas" + + preprocess_schemas(input_schemas, output_schemas) diff --git a/pyproject.toml b/pyproject.toml index d4d858a..78695e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,23 +43,4 @@ custom-file-header = """ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable -""" - -[tool.ruff] -line-length = 80 -indent-width = 2 - -[tool.ruff.format] -quote-style = "double" -indent-style = "space" -skip-magic-trailing-comma = false -line-ending = "auto" -docstring-code-format = true - -[tool.ruff.lint] -select = ["E", "F", "W", "B", "C4", "SIM", "N", "UP", "D", "PTH", "T20"] - -[tool.ruff.lint.isort] -combine-as-imports = true -force-sort-within-sections = true -case-sensitive = true +""" \ No newline at end of file diff --git a/src/ucp_sdk/__init__.py b/src/ucp_sdk/__init__.py deleted file mode 100644 index 6762e09..0000000 --- a/src/ucp_sdk/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# 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.""" - - -def hello() -> str: - return "Hello from ucp-python-sdk!" diff --git a/src/ucp_sdk/models/_internal.py b/src/ucp_sdk/models/_internal.py deleted file mode 100644 index edad471..0000000 --- a/src/ucp_sdk/models/_internal.py +++ /dev/null @@ -1,227 +0,0 @@ -# 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 AnyUrl, BaseModel, ConfigDict, Field, RootModel - - -class UcpCapability(RootModel[Any]): - root: Any = Field(..., title="UCP Capability") - """ - Schema for UCP capabilities and extensions. Extensions are capabilities with an 'extends' field. Uses reverse-domain naming for governance. - """ - - -class Base(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - name: str | None = Field( - None, pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$" - ) - """ - Stable capability identifier in reverse-domain notation (e.g., dev.ucp.shopping.checkout). Used in capability negotiation. - """ - version: Version | None = None - """ - Capability version in YYYY-MM-DD format. - """ - spec: AnyUrl | None = None - """ - URL to human-readable specification document. - """ - schema_: AnyUrl | None = Field(None, alias="schema") - """ - URL to JSON Schema for this capability's payload. - """ - extends: str | None = Field( - None, pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$" - ) - """ - Parent capability this extends. Present for extensions, absent for root capabilities. - """ - config: dict[str, Any] | None = None - """ - Capability-specific configuration (structure defined by each capability). - """ - - -class Discovery(Base): - """Full capability declaration for discovery profiles. Includes spec/schema URLs for agent fetching.""" - - model_config = ConfigDict( - extra="allow", - ) - - -class Response(Base): - """Capability reference in responses. Only name/version required to confirm active capabilities.""" - - model_config = ConfigDict( - extra="allow", - ) - - -class UcpMetadata(RootModel[Any]): - root: Any = Field(..., title="UCP Metadata") - """ - Protocol metadata for discovery profiles and responses. Uses slim schema pattern with context-specific required fields. - """ - - -class Version(RootModel[str]): - root: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$") - """ - UCP protocol version in YYYY-MM-DD format. - """ - - -class Services(RootModel[dict[str, "UcpService"]]): - """Service definitions keyed by reverse-domain service name.""" - - root: dict[str, UcpService] - - -class DiscoveryProfile(BaseModel): - """Full UCP metadata for /.well-known/ucp discovery.""" - - model_config = ConfigDict( - extra="allow", - ) - version: Version - services: Services - capabilities: list[Discovery] - """ - Supported capabilities and extensions. - """ - - -class ResponseCheckout(BaseModel): - """UCP metadata for checkout responses.""" - - model_config = ConfigDict( - extra="allow", - ) - version: Version - capabilities: list[Response] - """ - Active capabilities for this response. - """ - - -class ResponseOrder(BaseModel): - """UCP metadata for order responses. No payment handlers needed post-purchase.""" - - model_config = ConfigDict( - extra="allow", - ) - version: Version - capabilities: list[Response] - """ - Active capabilities for this response. - """ - - -class Rest(BaseModel): - """REST transport binding.""" - - model_config = ConfigDict( - extra="allow", - ) - schema_: AnyUrl = Field(..., alias="schema") - """ - URL to OpenAPI 3.x specification (JSON format) - """ - endpoint: AnyUrl - """ - Merchant's REST API endpoint - """ - - -class Mcp(BaseModel): - """MCP transport binding.""" - - model_config = ConfigDict( - extra="allow", - ) - schema_: AnyUrl = Field(..., alias="schema") - """ - URL to OpenRPC specification (JSON format) - """ - endpoint: AnyUrl - """ - Merchant's MCP endpoint - """ - - -class A2a(BaseModel): - """A2A transport binding.""" - - model_config = ConfigDict( - extra="allow", - ) - endpoint: AnyUrl - """ - Merchant's Agent Card endpoint - """ - - -class Embedded(BaseModel): - """Embedded transport binding (JSON-RPC 2.0 over postMessage). Unlike REST/MCP, the endpoint is per-capability (i.e. per-checkout via continue_url), not per-service.""" - - model_config = ConfigDict( - extra="allow", - ) - schema_: AnyUrl = Field(..., alias="schema") - """ - URL to OpenRPC specification (JSON format) defining the embedded protocol - """ - - -class UcpService(BaseModel): - """Schema for UCP service definitions. A service defines the API surface for a vertical (shopping, common, etc.) with transport bindings.""" - - model_config = ConfigDict( - extra="allow", - ) - version: Version - """ - Service version in YYYY-MM-DD format. - """ - spec: AnyUrl - """ - URL to service documentation. Origin MUST match namespace authority. - """ - rest: Rest | None = None - """ - REST transport binding - """ - mcp: Mcp | None = None - """ - MCP transport binding - """ - a2a: A2a | None = None - """ - A2A transport binding - """ - embedded: Embedded | None = None - """ - Embedded transport binding (JSON-RPC 2.0 over postMessage). Unlike REST/MCP, the endpoint is per-capability (i.e. per-checkout via continue_url), not per-service. - """ diff --git a/src/ucp_sdk/models/discovery/profile_schema.py b/src/ucp_sdk/models/discovery/profile_schema.py index bef9040..68ec227 100644 --- a/src/ucp_sdk/models/discovery/profile_schema.py +++ b/src/ucp_sdk/models/discovery/profile_schema.py @@ -19,77 +19,105 @@ from __future__ import annotations from typing import Literal -from pydantic import BaseModel, ConfigDict -from ..schemas.shopping.types import payment_handler_resp -from .._internal import DiscoveryProfile + +from pydantic import BaseModel, ConfigDict, Field, RootModel + +from ..schemas import ucp as ucp_1 class SigningKey(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - kid: str - """ + """ + Public key for signature verification in JWK format. + """ + + model_config = ConfigDict( + extra="allow", + ) + kid: str + """ Key ID. Referenced in signature headers to identify which key to use for verification. """ - kty: str - """ + kty: str + """ Key type (e.g., 'EC', 'RSA'). """ - crv: str | None = None - """ + crv: str | None = None + """ Curve name for EC keys (e.g., 'P-256'). """ - x: str | None = None - """ + x: str | None = None + """ X coordinate for EC public keys (base64url encoded). """ - y: str | None = None - """ + y: str | None = None + """ Y coordinate for EC public keys (base64url encoded). """ - n: str | None = None - """ + n: str | None = None + """ Modulus for RSA public keys (base64url encoded). """ - e: str | None = None - """ + e: str | None = None + """ Exponent for RSA public keys (base64url encoded). """ - use: Literal["sig", "enc"] | None = None - """ + use: Literal["sig", "enc"] | None = None + """ Key usage. Should be 'sig' for signing keys. """ - alg: str | None = None - """ + alg: str | None = None + """ Algorithm (e.g., 'ES256', 'RS256'). """ -class Payment(BaseModel): - """Payment configuration containing handlers.""" +class PlatformProfile(BaseModel): + """ + Full discovery profile for platforms. Exposes complete service, capability, and payment handler registries. + """ + + model_config = ConfigDict( + extra="allow", + ) + ucp: ucp_1.PlatformSchema + signing_keys: list[SigningKey] | None = None + """ + Public keys for signature verification (JWK format). Used to verify signed responses, webhooks, and other authenticated messages from this party. + """ + + +class BusinessProfile(BaseModel): + """ + Discovery profile for businesses/merchants. Subset of platform profile with business-specific configuration. + """ + + model_config = ConfigDict( + extra="allow", + ) + ucp: ucp_1.BusinessSchema + signing_keys: list[SigningKey] | None = None + """ + Public keys for signature verification (JWK format). Used to verify signed responses, webhooks, and other authenticated messages from this party. + """ + - model_config = ConfigDict( - extra="allow", - ) - handlers: list[payment_handler_resp.PaymentHandlerResponse] | None = None - """ - Payment handler definitions that describe how instruments can be collected +class UcpDiscoveryProfile(RootModel[PlatformProfile | BusinessProfile]): + root: PlatformProfile | BusinessProfile = Field(..., title="UCP Discovery Profile") + """ + Schema for UCP discovery profiles. Business profiles are hosted at /.well-known/ucp; platform profiles are hosted at URIs advertised in request headers. """ -class UcpDiscoveryProfile(BaseModel): - """Schema for UCP discovery profile returned from /.well-known/ucp.""" +class Base(BaseModel): + """ + Base discovery profile with shared properties for all profile types. + """ - model_config = ConfigDict( - extra="allow", - ) - ucp: DiscoveryProfile - payment: Payment | None = None - """ - Payment configuration containing handlers + model_config = ConfigDict( + extra="allow", + ) + ucp: ucp_1.Base + signing_keys: list[SigningKey] | None = None """ - signing_keys: list[SigningKey] | None = None - """ Public keys for signature verification (JWK format). Used to verify signed responses, webhooks, and other authenticated messages from this party. """ diff --git a/src/ucp_sdk/models/handlers/tokenization/openapi.py b/src/ucp_sdk/models/handlers/tokenization/openapi.py index e8b3b8c..66b2502 100644 --- a/src/ucp_sdk/models/handlers/tokenization/openapi.py +++ b/src/ucp_sdk/models/handlers/tokenization/openapi.py @@ -19,8 +19,9 @@ from __future__ import annotations from typing import Any + from pydantic import RootModel class Model(RootModel[Any]): - root: Any + root: Any diff --git a/src/ucp_sdk/models/ruff.toml b/src/ucp_sdk/models/ruff.toml new file mode 100644 index 0000000..9473cff --- /dev/null +++ b/src/ucp_sdk/models/ruff.toml @@ -0,0 +1,16 @@ +# Ruff configuration for generated models +# These are auto-generated files, so we're more lenient with style rules + +line-length = 120 +target-version = "py311" + +[lint] +select = ["E", "F", "I"] +ignore = ["E501"] + +[lint.pydocstyle] +convention = "google" + +[lint.per-file-ignores] +"__init__.py" = ["D104"] +"*.py" = ["D100", "D101", "D102", "D103", "D200", "D205", "D212"] diff --git a/src/ucp_sdk/models/schemas/capability.py b/src/ucp_sdk/models/schemas/capability.py index 26dc3d7..5145d47 100644 --- a/src/ucp_sdk/models/schemas/capability.py +++ b/src/ucp_sdk/models/schemas/capability.py @@ -18,6 +18,38 @@ from __future__ import annotations -from .._internal import Base, Discovery, Response, UcpCapability +from typing import Any -__all__ = ["Base", "Discovery", "Response", "UcpCapability"] +from pydantic import Field, RootModel + + +class UcpCapability(RootModel[Any]): + root: Any = Field(..., title="UCP Capability") + """ + Schema for UCP capabilities and extensions. Extensions are capabilities with an 'extends' field. Uses reverse-domain naming for governance. + """ + + +class PlatformSchema(RootModel[Any]): + root: Any = Field(..., title="Capability (Platform Schema)") + """ + Full capability declaration for platform-level discovery. Includes spec/schema URLs for agent fetching. + """ + + +class Base(RootModel[Any]): + root: Any + + +class BusinessSchema(RootModel[Base]): + root: Base = Field(..., title="Capability (Business Schema)") + """ + Capability configuration for business/merchant level. May include business-specific config overrides. + """ + + +class ResponseSchema(RootModel[Base]): + root: Base = Field(..., title="Capability (Response Schema)") + """ + Capability reference in responses. Only name/version required to confirm active capabilities. + """ diff --git a/src/ucp_sdk/models/schemas/payment_handler.py b/src/ucp_sdk/models/schemas/payment_handler.py new file mode 100644 index 0000000..5ddf666 --- /dev/null +++ b/src/ucp_sdk/models/schemas/payment_handler.py @@ -0,0 +1,55 @@ +# 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 Field, RootModel + + +class PaymentHandler(RootModel[Any]): + root: Any = Field(..., title="Payment Handler") + """ + Schema for UCP payment handlers. Handlers define how payment instruments are processed. + """ + + +class PlatformSchema(RootModel[Any]): + root: Any = Field(..., title="Payment Handler (Platform Schema)") + """ + Platform declaration for discovery profiles. May include partial config state required for discovery. + """ + + +class Base(RootModel[Any]): + root: Any + + +class BusinessSchema(RootModel[Base]): + root: Base = Field(..., title="Payment Handler (Business Schema)") + """ + Business declaration for discovery profiles. May include partial config state required for discovery. + """ + + +class ResponseSchema(RootModel[Base]): + root: Base = Field(..., title="Payment Handler (Response Schema)") + """ + Handler reference in responses. May include full config state for runtime usage of the handler. + """ diff --git a/src/ucp_sdk/models/schemas/service.py b/src/ucp_sdk/models/schemas/service.py new file mode 100644 index 0000000..227ca16 --- /dev/null +++ b/src/ucp_sdk/models/schemas/service.py @@ -0,0 +1,197 @@ +# 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 BaseModel, ConfigDict, Field, RootModel + +from .transports import embedded_config + + +class UcpService(RootModel[Any]): + root: Any = Field(..., title="UCP Service") + """ + Service binding for a specific transport. Each transport binding is a separate entry in the service array. + """ + + +class PlatformSchema(BaseModel): + """ + Full service declaration for platform-level discovery. Different transports require different fields. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["rest"] = "rest" + + +class PlatformSchema5(BaseModel): + """ + Full service declaration for platform-level discovery. Different transports require different fields. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["mcp"] = "mcp" + + +class PlatformSchema6(BaseModel): + """ + Full service declaration for platform-level discovery. Different transports require different fields. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["a2a"] = "a2a" + + +class PlatformSchema7(BaseModel): + """ + Full service declaration for platform-level discovery. Different transports require different fields. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["embedded"] = "embedded" + + +class PlatformSchema3(RootModel[PlatformSchema | PlatformSchema5 | PlatformSchema6 | PlatformSchema7]): + root: PlatformSchema | PlatformSchema5 | PlatformSchema6 | PlatformSchema7 = Field( + ..., title="Service (Platform Schema)" + ) + """ + Full service declaration for platform-level discovery. Different transports require different fields. + """ + + +class BusinessSchema(BaseModel): + """ + Service binding for business/merchant configuration. May override platform endpoints. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["rest"] = "rest" + + +class BusinessSchema4(BaseModel): + """ + Service binding for business/merchant configuration. May override platform endpoints. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["mcp"] = "mcp" + + +class BusinessSchema5(BaseModel): + """ + Service binding for business/merchant configuration. May override platform endpoints. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["a2a"] = "a2a" + + +class BusinessSchema6(BaseModel): + """ + Service binding for business/merchant configuration. May override platform endpoints. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["embedded"] = "embedded" + config: embedded_config.EmbeddedTransportConfig | None = None + + +class BusinessSchema2(RootModel[BusinessSchema | BusinessSchema4 | BusinessSchema5 | BusinessSchema6]): + root: BusinessSchema | BusinessSchema4 | BusinessSchema5 | BusinessSchema6 = Field( + ..., title="Service (Business Schema)" + ) + """ + Service binding for business/merchant configuration. May override platform endpoints. + """ + + +class ResponseSchema(BaseModel): + """ + Service binding in API responses. Includes per-resource transport configuration via typed config. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["rest"] = "rest" + + +class ResponseSchema4(BaseModel): + """ + Service binding in API responses. Includes per-resource transport configuration via typed config. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["mcp"] = "mcp" + + +class ResponseSchema5(BaseModel): + """ + Service binding in API responses. Includes per-resource transport configuration via typed config. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["a2a"] = "a2a" + + +class ResponseSchema6(BaseModel): + """ + Service binding in API responses. Includes per-resource transport configuration via typed config. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["embedded"] = "embedded" + config: embedded_config.EmbeddedTransportConfig | None = None + + +class ResponseSchema2(RootModel[ResponseSchema | ResponseSchema4 | ResponseSchema5 | ResponseSchema6]): + root: ResponseSchema | ResponseSchema4 | ResponseSchema5 | ResponseSchema6 = Field( + ..., title="Service (Response Schema)" + ) + """ + Service binding in API responses. Includes per-resource transport configuration via typed config. + """ + + +class Base(RootModel[Any]): + root: Any diff --git a/src/ucp_sdk/models/schemas/shopping/ap2_mandate.py b/src/ucp_sdk/models/schemas/shopping/ap2_mandate.py index a41ef4c..cecaa1c 100644 --- a/src/ucp_sdk/models/schemas/shopping/ap2_mandate.py +++ b/src/ucp_sdk/models/schemas/shopping/ap2_mandate.py @@ -19,109 +19,89 @@ from __future__ import annotations from typing import Any, Literal + from pydantic import BaseModel, ConfigDict, Field, RootModel -from .checkout_resp import CheckoutResponse class Ap2MandateExtension(RootModel[Any]): - root: Any = Field(..., title="AP2 Mandate Extension") - """ + root: Any = Field(..., title="AP2 Mandate Extension") + """ Extends Checkout with cryptographic mandate support for non-repudiable authorization per the AP2 protocol. Uses embedded signature model with ap2 namespace. """ class MerchantAuthorization(RootModel[str]): - root: str = Field( - ..., - pattern="^[A-Za-z0-9_-]+\\.\\.[A-Za-z0-9_-]+$", - title="Merchant Authorization", - ) - """ + root: str = Field( + ..., + pattern="^[A-Za-z0-9_-]+\\.\\.[A-Za-z0-9_-]+$", + title="Merchant Authorization", + ) + """ JWS Detached Content signature (RFC 7515 Appendix F) over the checkout response body (excluding ap2 field). Format: `..`. The header MUST contain 'alg' (ES256/ES384/ES512) and 'kid' claims. The signature covers both the header and JCS-canonicalized checkout payload. """ class CheckoutMandate(RootModel[str]): - root: str = Field( - ..., - pattern="^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]*\\.[A-Za-z0-9_-]+(~[A-Za-z0-9_-]+)*$", - title="Checkout Mandate", - ) - """ + root: str = Field( + ..., + pattern="^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]*\\.[A-Za-z0-9_-]+(~[A-Za-z0-9_-]+)*$", + title="Checkout Mandate", + ) + """ SD-JWT+kb credential in `ap2.checkout_mandate`. Proving user authorization for the checkout. Contains the full checkout including `ap2.merchant_authorization`. """ -class Ap2CheckoutResponse(BaseModel): - """The ap2 object included in checkout responses when AP2 is negotiated.""" +class Ap2WithMerchantAuthorization(BaseModel): + """ + AP2 extension data including merchant authorization. + """ - model_config = ConfigDict( - extra="allow", - ) - merchant_authorization: MerchantAuthorization - """ + model_config = ConfigDict( + extra="allow", + ) + merchant_authorization: str | None = None + """ Merchant's signature proving checkout terms are authentic. """ -class Ap2CompleteRequest(BaseModel): - """The ap2 object included in complete_checkout requests when AP2 is negotiated.""" +class Ap2WithCheckoutMandate(BaseModel): + """ + AP2 extension data including checkout mandate. + """ - model_config = ConfigDict( - extra="allow", - ) - checkout_mandate: CheckoutMandate - """ + model_config = ConfigDict( + extra="allow", + ) + checkout_mandate: str | None = None + """ SD-JWT+kb proving user authorized this checkout. """ class ErrorCode( - RootModel[ - Literal[ - "mandate_required", - "agent_missing_key", - "mandate_invalid_signature", - "mandate_expired", - "mandate_scope_mismatch", - "merchant_authorization_invalid", - "merchant_authorization_missing", + RootModel[ + Literal[ + "mandate_required", + "agent_missing_key", + "mandate_invalid_signature", + "mandate_expired", + "mandate_scope_mismatch", + "merchant_authorization_invalid", + "merchant_authorization_missing", + ] ] - ] ): - root: Literal[ - "mandate_required", - "agent_missing_key", - "mandate_invalid_signature", - "mandate_expired", - "mandate_scope_mismatch", - "merchant_authorization_invalid", - "merchant_authorization_missing", - ] = Field(..., title="AP2 Error Code") - """ - Error codes specific to AP2 mandate verification. - """ - - -class CompleteRequestWithAp2(BaseModel): - """Extension fields for complete_checkout when AP2 is negotiated.""" - - model_config = ConfigDict( - extra="allow", - ) - ap2: Ap2CompleteRequest | None = None - """ - AP2 extension data including checkout mandate. + root: Literal[ + "mandate_required", + "agent_missing_key", + "mandate_invalid_signature", + "mandate_expired", + "mandate_scope_mismatch", + "merchant_authorization_invalid", + "merchant_authorization_missing", + ] = Field(..., title="AP2 Error Code") """ - - -class CheckoutResponseWithAp2(CheckoutResponse): - """Checkout extended with AP2 embedded signature support.""" - - model_config = ConfigDict( - extra="allow", - ) - ap2: Ap2CheckoutResponse | None = None - """ - AP2 extension data including merchant authorization. + Error codes specific to AP2 mandate verification. """ diff --git a/src/ucp_sdk/models/schemas/shopping/buyer_consent_resp.py b/src/ucp_sdk/models/schemas/shopping/buyer_consent.py similarity index 56% rename from src/ucp_sdk/models/schemas/shopping/buyer_consent_resp.py rename to src/ucp_sdk/models/schemas/shopping/buyer_consent.py index f2117fb..d7b3c74 100644 --- a/src/ucp_sdk/models/schemas/shopping/buyer_consent_resp.py +++ b/src/ucp_sdk/models/schemas/shopping/buyer_consent.py @@ -19,61 +19,38 @@ from __future__ import annotations from typing import Any + from pydantic import BaseModel, ConfigDict, Field, RootModel -from .types.buyer import Buyer as Buyer_1 -from .checkout_resp import CheckoutResponse -class BuyerConsentExtensionResponse(RootModel[Any]): - root: Any = Field(..., title="Buyer Consent Extension Response") - """ +class BuyerConsentExtension(RootModel[Any]): + root: Any = Field(..., title="Buyer Consent Extension") + """ Extends Checkout with buyer consent tracking for privacy compliance via the buyer object. """ class Consent(BaseModel): - """User consent states for data processing.""" + """ + User consent states for data processing + """ - model_config = ConfigDict( - extra="allow", - ) - analytics: bool | None = None - """ + model_config = ConfigDict( + extra="allow", + ) + analytics: bool | None = None + """ Consent for analytics and performance tracking. """ - preferences: bool | None = None - """ + preferences: bool | None = None + """ Consent for storing user preferences. """ - marketing: bool | None = None - """ - Consent for marketing communications. + marketing: bool | None = None """ - sale_of_data: bool | None = None - """ - Consent for selling data to third parties (CCPA). + Consent for marketing communications. """ - - -class Buyer(Buyer_1): - """Buyer object extended with consent tracking.""" - - model_config = ConfigDict( - extra="allow", - ) - consent: Consent | None = None - """ - Consent tracking fields. + sale_of_data: bool | None = None """ - - -class Checkout(CheckoutResponse): - """Checkout extended with consent tracking via buyer object.""" - - model_config = ConfigDict( - extra="allow", - ) - buyer: Buyer | None = None - """ - Buyer with consent tracking. + Consent for selling data to third parties (CCPA). """ diff --git a/src/ucp_sdk/models/schemas/shopping/buyer_consent_create_req.py b/src/ucp_sdk/models/schemas/shopping/buyer_consent_create_req.py deleted file mode 100644 index f1d2dae..0000000 --- a/src/ucp_sdk/models/schemas/shopping/buyer_consent_create_req.py +++ /dev/null @@ -1,79 +0,0 @@ -# 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, RootModel -from .types.buyer import Buyer as Buyer_1 -from .checkout_create_req import CheckoutCreateRequest - - -class BuyerConsentExtensionCreateRequest(RootModel[Any]): - root: Any = Field(..., title="Buyer Consent Extension Create Request") - """ - Extends Checkout with buyer consent tracking for privacy compliance via the buyer object. - """ - - -class Consent(BaseModel): - """User consent states for data processing.""" - - model_config = ConfigDict( - extra="allow", - ) - analytics: bool | None = None - """ - Consent for analytics and performance tracking. - """ - preferences: bool | None = None - """ - Consent for storing user preferences. - """ - marketing: bool | None = None - """ - Consent for marketing communications. - """ - sale_of_data: bool | None = None - """ - Consent for selling data to third parties (CCPA). - """ - - -class Buyer(Buyer_1): - """Buyer object extended with consent tracking.""" - - model_config = ConfigDict( - extra="allow", - ) - consent: Consent | None = None - """ - Consent tracking fields. - """ - - -class Checkout(CheckoutCreateRequest): - """Checkout extended with consent tracking via buyer object.""" - - model_config = ConfigDict( - extra="allow", - ) - buyer: Buyer | None = None - """ - Buyer with consent tracking. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/buyer_consent_update_req.py b/src/ucp_sdk/models/schemas/shopping/buyer_consent_update_req.py deleted file mode 100644 index 33d4e84..0000000 --- a/src/ucp_sdk/models/schemas/shopping/buyer_consent_update_req.py +++ /dev/null @@ -1,79 +0,0 @@ -# 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, RootModel -from .types.buyer import Buyer as Buyer_1 -from .checkout_update_req import CheckoutUpdateRequest - - -class BuyerConsentExtensionUpdateRequest(RootModel[Any]): - root: Any = Field(..., title="Buyer Consent Extension Update Request") - """ - Extends Checkout with buyer consent tracking for privacy compliance via the buyer object. - """ - - -class Consent(BaseModel): - """User consent states for data processing.""" - - model_config = ConfigDict( - extra="allow", - ) - analytics: bool | None = None - """ - Consent for analytics and performance tracking. - """ - preferences: bool | None = None - """ - Consent for storing user preferences. - """ - marketing: bool | None = None - """ - Consent for marketing communications. - """ - sale_of_data: bool | None = None - """ - Consent for selling data to third parties (CCPA). - """ - - -class Buyer(Buyer_1): - """Buyer object extended with consent tracking.""" - - model_config = ConfigDict( - extra="allow", - ) - consent: Consent | None = None - """ - Consent tracking fields. - """ - - -class Checkout(CheckoutUpdateRequest): - """Checkout extended with consent tracking via buyer object.""" - - model_config = ConfigDict( - extra="allow", - ) - buyer: Buyer | None = None - """ - Buyer with consent tracking. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/cart.py b/src/ucp_sdk/models/schemas/shopping/cart.py new file mode 100644 index 0000000..d66c2c7 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/cart.py @@ -0,0 +1,128 @@ +# 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 AnyUrl, AwareDatetime, BaseModel, ConfigDict + +from .types import line_item, link, message, total + + +class Context(BaseModel): + """ + Buyer signals for localization (country, region, postal_code). Merchant uses for pricing, availability, currency. Falls back to geo-IP if omitted. + """ + + model_config = ConfigDict( + extra="allow", + ) + address_country: str | None = None + """ + 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. + """ + address_region: 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. + """ + postal_code: str | None = None + """ + The postal code. For example, 94043. Optional hint for regional refinement—higher-resolution data (e.g., shipping address) supersedes this value. + """ + intent: str | None = None + """ + Background context describing buyer's intent (e.g., 'looking for a gift under $50', 'need something durable for outdoor use'). Informs relevance, recommendations, and personalization. + """ + + +class Buyer(BaseModel): + """ + Optional buyer information for personalized estimates. + """ + + model_config = ConfigDict( + extra="allow", + ) + first_name: str | None = None + """ + First name of the buyer. + """ + last_name: str | None = None + """ + Last name of the buyer. + """ + email: str | None = None + """ + Email of the buyer. + """ + phone_number: str | None = None + """ + E.164 standard. + """ + + +class Cart(BaseModel): + """ + Shopping cart with estimated pricing before checkout. Lightweight pre-purchase exploration with no payment info or complex status states. Cart exists (200) or doesn't (404). + """ + + model_config = ConfigDict( + extra="allow", + ) + ucp: Any + id: str + """ + Unique cart identifier. + """ + line_items: list[line_item.LineItem] + """ + Cart line items. Same structure as checkout. Full replacement on update. + """ + context: Context | None = None + """ + Buyer signals for localization (country, region, postal_code). Merchant uses for pricing, availability, currency. Falls back to geo-IP if omitted. + """ + buyer: Buyer | None = None + """ + Optional buyer information for personalized estimates. + """ + currency: str + """ + ISO 4217 currency code. Determined by merchant based on context or geo-IP. + """ + totals: list[total.Total] + """ + Estimated cost breakdown. May be partial if shipping/tax not yet calculable. + """ + messages: list[message.Message] | None = None + """ + Validation messages, warnings, or informational notices. + """ + links: list[link.Link] | None = None + """ + Optional merchant links (policies, FAQs). + """ + continue_url: AnyUrl | None = None + """ + URL for cart handoff and session recovery. Enables sharing and human-in-the-loop flows. + """ + expires_at: AwareDatetime | None = None + """ + Cart expiry timestamp (RFC 3339). Optional. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/checkout.py b/src/ucp_sdk/models/schemas/shopping/checkout.py new file mode 100644 index 0000000..62a0d69 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/checkout.py @@ -0,0 +1,190 @@ +# 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 AnyUrl, AwareDatetime, BaseModel, ConfigDict, Field + +from .types import line_item, link, message, payment_instrument, total + + +class Ucp(BaseModel): + """ + UCP metadata for checkout responses. + """ + + model_config = ConfigDict( + extra="allow", + ) + version: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$") + """ + UCP version in YYYY-MM-DD format. + """ + services: Any | None = None + capabilities: Any | None = None + payment_handlers: Any + + +class Buyer(BaseModel): + """ + Representation of the buyer. + """ + + model_config = ConfigDict( + extra="allow", + ) + first_name: str | None = None + """ + First name of the buyer. + """ + last_name: str | None = None + """ + Last name of the buyer. + """ + email: str | None = None + """ + Email of the buyer. + """ + phone_number: str | None = None + """ + E.164 standard. + """ + + +class Context(BaseModel): + """ + Provisional buyer signals for relevance and localization: product availability, pricing, currency, tax, shipping, payment methods, and eligibility (e.g., student or affiliation discounts). Businesses SHOULD use these values when authoritative data (e.g., address) is absent, and MAY ignore unsupported values without returning errors. 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. Platforms SHOULD progressively enhance context throughout the buyer journey. + """ + + model_config = ConfigDict( + extra="allow", + ) + address_country: str | None = None + """ + 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. + """ + address_region: 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. + """ + postal_code: str | None = None + """ + The postal code. For example, 94043. Optional hint for regional refinement—higher-resolution data (e.g., shipping address) supersedes this value. + """ + intent: str | None = None + """ + Background context describing buyer's intent (e.g., 'looking for a gift under $50', 'need something durable for outdoor use'). Informs relevance, recommendations, and personalization. + """ + + +class Order(BaseModel): + """ + Details about an order created for this checkout session. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + Unique order identifier. + """ + permalink_url: AnyUrl + """ + Permalink to access the order on merchant site. + """ + + +class Payment(BaseModel): + """ + Payment configuration containing handlers. + """ + + model_config = ConfigDict( + extra="allow", + ) + instruments: list[payment_instrument.SelectedPaymentInstrument] | None = None + """ + The payment instruments available for this payment. Each instrument is associated with a specific handler via the handler_id field. Handlers can extend the base payment_instrument schema to add handler-specific fields. + """ + + +class Checkout(BaseModel): + """ + Base checkout schema. Extensions compose onto this using allOf. + """ + + model_config = ConfigDict( + extra="allow", + ) + ucp: Ucp + id: str + """ + Unique identifier of the checkout session. + """ + line_items: list[line_item.LineItem] + """ + List of line items being checked out. + """ + buyer: Buyer | None = None + """ + Representation of the buyer. + """ + context: Context | None = None + status: Literal[ + "incomplete", + "requires_escalation", + "ready_for_complete", + "complete_in_progress", + "completed", + "canceled", + ] + """ + Checkout state indicating the current phase and required action. See Checkout Status lifecycle documentation for state transition details. + """ + currency: str + """ + ISO 4217 currency code reflecting the merchant's market determination. Derived from address, context, and geo IP—buyers provide signals, merchants determine currency. + """ + totals: list[total.Total] + """ + Different cart totals. + """ + messages: list[message.Message] | None = None + """ + List of messages with error and info about the checkout session state. + """ + links: list[link.Link] + """ + Links to be displayed by the platform (Privacy Policy, TOS). Mandatory for legal compliance. + """ + expires_at: AwareDatetime | None = None + """ + RFC 3339 expiry timestamp. Default TTL is 6 hours from creation if not sent. + """ + continue_url: AnyUrl | None = None + """ + URL for checkout handoff and session recovery. MUST be provided when status is requires_escalation. See specification for format and availability requirements. + """ + payment: Payment | None = None + order: Order | None = None + """ + Details about an order created for this checkout session. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/checkout_create_req.py b/src/ucp_sdk/models/schemas/shopping/checkout_create_req.py deleted file mode 100644 index ad53d1f..0000000 --- a/src/ucp_sdk/models/schemas/shopping/checkout_create_req.py +++ /dev/null @@ -1,44 +0,0 @@ -# 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 .types import buyer as buyer_1, line_item_create_req -from . import payment_create_req - - -class CheckoutCreateRequest(BaseModel): - """Base checkout schema. Extensions compose onto this using allOf.""" - - model_config = ConfigDict( - extra="allow", - ) - line_items: list[line_item_create_req.LineItemCreateRequest] - """ - List of line items being checked out. - """ - buyer: buyer_1.Buyer | None = None - """ - Representation of the buyer. - """ - currency: str - """ - ISO 4217 currency code. - """ - payment: payment_create_req.PaymentCreateRequest diff --git a/src/ucp_sdk/models/schemas/shopping/checkout_resp.py b/src/ucp_sdk/models/schemas/shopping/checkout_resp.py deleted file mode 100644 index 46085e3..0000000 --- a/src/ucp_sdk/models/schemas/shopping/checkout_resp.py +++ /dev/null @@ -1,93 +0,0 @@ -# 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, AwareDatetime, BaseModel, ConfigDict -from ..._internal import ResponseCheckout -from .types import ( - buyer as buyer_1, - line_item_resp, - link, - message, - order_confirmation, - total_resp, -) -from . import payment_resp - - -class CheckoutResponse(BaseModel): - """Base checkout schema. Extensions compose onto this using allOf.""" - - model_config = ConfigDict( - extra="allow", - ) - ucp: ResponseCheckout - id: str - """ - Unique identifier of the checkout session. - """ - line_items: list[line_item_resp.LineItemResponse] - """ - List of line items being checked out. - """ - buyer: buyer_1.Buyer | None = None - """ - Representation of the buyer. - """ - status: Literal[ - "incomplete", - "requires_escalation", - "ready_for_complete", - "complete_in_progress", - "completed", - "canceled", - ] - """ - Checkout state indicating the current phase and required action. See Checkout Status lifecycle documentation for state transition details. - """ - currency: str - """ - ISO 4217 currency code. - """ - totals: list[total_resp.TotalResponse] - """ - Different cart totals. - """ - messages: list[message.Message] | None = None - """ - List of messages with error and info about the checkout session state. - """ - links: list[link.Link] - """ - Links to be displayed by the platform (Privacy Policy, TOS). Mandatory for legal compliance. - """ - expires_at: AwareDatetime | None = None - """ - RFC 3339 expiry timestamp. Default TTL is 6 hours from creation if not sent. - """ - continue_url: AnyUrl | None = None - """ - URL for checkout handoff and session recovery. MUST be provided when status is requires_escalation. See specification for format and availability requirements. - """ - payment: payment_resp.PaymentResponse - order: order_confirmation.OrderConfirmation | None = None - """ - Details about an order created for this checkout session. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/checkout_update_req.py b/src/ucp_sdk/models/schemas/shopping/checkout_update_req.py deleted file mode 100644 index 6234448..0000000 --- a/src/ucp_sdk/models/schemas/shopping/checkout_update_req.py +++ /dev/null @@ -1,48 +0,0 @@ -# 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 .types import buyer as buyer_1, line_item_update_req -from . import payment_update_req - - -class CheckoutUpdateRequest(BaseModel): - """Base checkout schema. Extensions compose onto this using allOf.""" - - model_config = ConfigDict( - extra="allow", - ) - id: str - """ - Unique identifier of the checkout session. - """ - line_items: list[line_item_update_req.LineItemUpdateRequest] - """ - List of line items being checked out. - """ - buyer: buyer_1.Buyer | None = None - """ - Representation of the buyer. - """ - currency: str - """ - ISO 4217 currency code. - """ - payment: payment_update_req.PaymentUpdateRequest diff --git a/src/ucp_sdk/models/schemas/shopping/discount_resp.py b/src/ucp_sdk/models/schemas/shopping/discount.py similarity index 63% rename from src/ucp_sdk/models/schemas/shopping/discount_resp.py rename to src/ucp_sdk/models/schemas/shopping/discount.py index 86fd012..952ded6 100644 --- a/src/ucp_sdk/models/schemas/shopping/discount_resp.py +++ b/src/ucp_sdk/models/schemas/shopping/discount.py @@ -19,89 +19,86 @@ from __future__ import annotations from typing import Any, Literal + from pydantic import BaseModel, ConfigDict, Field, RootModel -from .checkout_resp import CheckoutResponse -class DiscountExtensionResponse(RootModel[Any]): - root: Any = Field(..., title="Discount Extension Response") - """ +class DiscountExtension(RootModel[Any]): + root: Any = Field(..., title="Discount Extension") + """ Extends Checkout with discount code support, enabling agents to apply promotional, loyalty, referral, and other discount codes. """ class Allocation(BaseModel): - """Breakdown of how a discount amount was allocated to a specific target.""" + """ + Breakdown of how a discount amount was allocated to a specific target. + """ - model_config = ConfigDict( - extra="allow", - ) - path: str - """ + model_config = ConfigDict( + extra="allow", + ) + path: str + """ JSONPath to the allocation target (e.g., '$.line_items[0]', '$.totals.shipping'). """ - amount: int = Field(..., ge=0) - """ + amount: int = Field(..., ge=0) + """ Amount allocated to this target in minor (cents) currency units. """ class AppliedDiscount(BaseModel): - """A discount that was successfully applied.""" + """ + A discount that was successfully applied. + """ - model_config = ConfigDict( - extra="allow", - ) - code: str | None = None - """ + model_config = ConfigDict( + extra="allow", + ) + code: str | None = None + """ The discount code. Omitted for automatic discounts. """ - title: str - """ + title: str + """ Human-readable discount name (e.g., 'Summer Sale 20% Off'). """ - amount: int = Field(..., ge=0) - """ + amount: int = Field(..., ge=0) + """ Total discount amount in minor (cents) currency units. """ - automatic: bool | None = False - """ + automatic: bool | None = False + """ True if applied automatically by merchant rules (no code required). """ - method: Literal["each", "across"] | None = None - """ + method: Literal["each", "across"] | None = None + """ Allocation method. 'each' = applied independently per item. 'across' = split proportionally by value. """ - priority: int | None = Field(None, ge=1) - """ + priority: int | None = Field(None, ge=1) + """ Stacking order for discount calculation. Lower numbers applied first (1 = first). """ - allocations: list[Allocation] | None = None - """ + allocations: list[Allocation] | None = None + """ Breakdown of where this discount was allocated. Sum of allocation amounts equals total amount. """ class DiscountsObject(BaseModel): - """Discount codes input and applied discounts output.""" + """ + Discount codes input and applied discounts output. + """ - model_config = ConfigDict( - extra="allow", - ) - codes: list[str] | None = None - """ + model_config = ConfigDict( + extra="allow", + ) + codes: list[str] | None = None + """ Discount codes to apply. Case-insensitive. Replaces previously submitted codes. Send empty array to clear. """ - applied: list[AppliedDiscount] | None = None - """ + applied: list[AppliedDiscount] | None = None + """ Discounts successfully applied (code-based and automatic). """ - - -class Checkout(CheckoutResponse): - """Checkout extended with discount capability.""" - - model_config = ConfigDict( - extra="allow", - ) - discounts: DiscountsObject | None = None diff --git a/src/ucp_sdk/models/schemas/shopping/discount_create_req.py b/src/ucp_sdk/models/schemas/shopping/discount_create_req.py deleted file mode 100644 index 7595bb1..0000000 --- a/src/ucp_sdk/models/schemas/shopping/discount_create_req.py +++ /dev/null @@ -1,107 +0,0 @@ -# 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 BaseModel, ConfigDict, Field, RootModel -from .checkout_create_req import CheckoutCreateRequest - - -class DiscountExtensionCreateRequest(RootModel[Any]): - root: Any = Field(..., title="Discount Extension Create Request") - """ - Extends Checkout with discount code support, enabling agents to apply promotional, loyalty, referral, and other discount codes. - """ - - -class Allocation(BaseModel): - """Breakdown of how a discount amount was allocated to a specific target.""" - - model_config = ConfigDict( - extra="allow", - ) - path: str - """ - JSONPath to the allocation target (e.g., '$.line_items[0]', '$.totals.shipping'). - """ - amount: int = Field(..., ge=0) - """ - Amount allocated to this target in minor (cents) currency units. - """ - - -class AppliedDiscount(BaseModel): - """A discount that was successfully applied.""" - - model_config = ConfigDict( - extra="allow", - ) - code: str | None = None - """ - The discount code. Omitted for automatic discounts. - """ - title: str - """ - Human-readable discount name (e.g., 'Summer Sale 20% Off'). - """ - amount: int = Field(..., ge=0) - """ - Total discount amount in minor (cents) currency units. - """ - automatic: bool | None = False - """ - True if applied automatically by merchant rules (no code required). - """ - method: Literal["each", "across"] | None = None - """ - Allocation method. 'each' = applied independently per item. 'across' = split proportionally by value. - """ - priority: int | None = Field(None, ge=1) - """ - Stacking order for discount calculation. Lower numbers applied first (1 = first). - """ - allocations: list[Allocation] | None = None - """ - Breakdown of where this discount was allocated. Sum of allocation amounts equals total amount. - """ - - -class DiscountsObject(BaseModel): - """Discount codes input and applied discounts output.""" - - model_config = ConfigDict( - extra="allow", - ) - codes: list[str] | None = None - """ - Discount codes to apply. Case-insensitive. Replaces previously submitted codes. Send empty array to clear. - """ - applied: list[AppliedDiscount] | None = None - """ - Discounts successfully applied (code-based and automatic). - """ - - -class Checkout(CheckoutCreateRequest): - """Checkout extended with discount capability.""" - - model_config = ConfigDict( - extra="allow", - ) - discounts: DiscountsObject | None = None diff --git a/src/ucp_sdk/models/schemas/shopping/discount_update_req.py b/src/ucp_sdk/models/schemas/shopping/discount_update_req.py deleted file mode 100644 index a724dc2..0000000 --- a/src/ucp_sdk/models/schemas/shopping/discount_update_req.py +++ /dev/null @@ -1,107 +0,0 @@ -# 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 BaseModel, ConfigDict, Field, RootModel -from .checkout_update_req import CheckoutUpdateRequest - - -class DiscountExtensionUpdateRequest(RootModel[Any]): - root: Any = Field(..., title="Discount Extension Update Request") - """ - Extends Checkout with discount code support, enabling agents to apply promotional, loyalty, referral, and other discount codes. - """ - - -class Allocation(BaseModel): - """Breakdown of how a discount amount was allocated to a specific target.""" - - model_config = ConfigDict( - extra="allow", - ) - path: str - """ - JSONPath to the allocation target (e.g., '$.line_items[0]', '$.totals.shipping'). - """ - amount: int = Field(..., ge=0) - """ - Amount allocated to this target in minor (cents) currency units. - """ - - -class AppliedDiscount(BaseModel): - """A discount that was successfully applied.""" - - model_config = ConfigDict( - extra="allow", - ) - code: str | None = None - """ - The discount code. Omitted for automatic discounts. - """ - title: str - """ - Human-readable discount name (e.g., 'Summer Sale 20% Off'). - """ - amount: int = Field(..., ge=0) - """ - Total discount amount in minor (cents) currency units. - """ - automatic: bool | None = False - """ - True if applied automatically by merchant rules (no code required). - """ - method: Literal["each", "across"] | None = None - """ - Allocation method. 'each' = applied independently per item. 'across' = split proportionally by value. - """ - priority: int | None = Field(None, ge=1) - """ - Stacking order for discount calculation. Lower numbers applied first (1 = first). - """ - allocations: list[Allocation] | None = None - """ - Breakdown of where this discount was allocated. Sum of allocation amounts equals total amount. - """ - - -class DiscountsObject(BaseModel): - """Discount codes input and applied discounts output.""" - - model_config = ConfigDict( - extra="allow", - ) - codes: list[str] | None = None - """ - Discount codes to apply. Case-insensitive. Replaces previously submitted codes. Send empty array to clear. - """ - applied: list[AppliedDiscount] | None = None - """ - Discounts successfully applied (code-based and automatic). - """ - - -class Checkout(CheckoutUpdateRequest): - """Checkout extended with discount capability.""" - - model_config = ConfigDict( - extra="allow", - ) - discounts: DiscountsObject | None = None diff --git a/src/ucp_sdk/models/schemas/shopping/fulfillment/__init__.py b/src/ucp_sdk/models/schemas/shopping/fulfillment/__init__.py new file mode 100644 index 0000000..3390b86 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/fulfillment/__init__.py @@ -0,0 +1,58 @@ +# 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 Field, RootModel + +from ..types import ( + fulfillment, + fulfillment_available_method, + fulfillment_group, + fulfillment_method, + fulfillment_option, +) + + +class FulfillmentExtension(RootModel[Any]): + root: Any = Field(..., title="Fulfillment Extension") + """ + Extends Checkout with fulfillment support using methods, destinations, and groups. + """ + + +class FulfillmentAvailableMethod(RootModel[fulfillment_available_method.FulfillmentAvailableMethod]): + root: fulfillment_available_method.FulfillmentAvailableMethod + + +class FulfillmentOption(RootModel[fulfillment_option.FulfillmentOption]): + root: fulfillment_option.FulfillmentOption + + +class FulfillmentGroup(RootModel[fulfillment_group.FulfillmentGroup]): + root: fulfillment_group.FulfillmentGroup + + +class FulfillmentMethod(RootModel[fulfillment_method.FulfillmentMethod]): + root: fulfillment_method.FulfillmentMethod + + +class Fulfillment(RootModel[fulfillment.Fulfillment]): + root: fulfillment.Fulfillment diff --git a/src/ucp_sdk/models/__init__.py b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/__init__.py similarity index 100% rename from src/ucp_sdk/models/__init__.py rename to src/ucp_sdk/models/schemas/shopping/fulfillment/dev/__init__.py diff --git a/src/ucp_sdk/models/services/service_schema.py b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/__init__.py similarity index 80% rename from src/ucp_sdk/models/services/service_schema.py rename to src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/__init__.py index 3272570..421dc21 100644 --- a/src/ucp_sdk/models/services/service_schema.py +++ b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/__init__.py @@ -16,8 +16,3 @@ # pylint: disable=all # pyformat: disable -from __future__ import annotations - -from .._internal import A2a, Embedded, Mcp, Rest, UcpService - -__all__ = ["A2a", "Embedded", "Mcp", "Rest", "UcpService"] diff --git a/src/ucp_sdk/models/services/shopping/rest_openapi.py b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/shopping.py similarity index 93% rename from src/ucp_sdk/models/services/shopping/rest_openapi.py rename to src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/shopping.py index e8b3b8c..cc3e02d 100644 --- a/src/ucp_sdk/models/services/shopping/rest_openapi.py +++ b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/shopping.py @@ -19,8 +19,9 @@ from __future__ import annotations from typing import Any + from pydantic import RootModel -class Model(RootModel[Any]): - root: Any +class Fulfillment(RootModel[Any]): + root: Any diff --git a/src/ucp_sdk/models/schemas/shopping/fulfillment_create_req.py b/src/ucp_sdk/models/schemas/shopping/fulfillment_create_req.py deleted file mode 100644 index 3e1abf8..0000000 --- a/src/ucp_sdk/models/schemas/shopping/fulfillment_create_req.py +++ /dev/null @@ -1,77 +0,0 @@ -# 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 ConfigDict, Field, RootModel -from .types import ( - fulfillment_available_method_req, - fulfillment_group_create_req, - fulfillment_method_create_req, - fulfillment_option_req, - fulfillment_req, -) -from .checkout_create_req import CheckoutCreateRequest - - -class FulfillmentExtensionCreateRequest(RootModel[Any]): - root: Any = Field(..., title="Fulfillment Extension Create Request") - """ - Extends Checkout with fulfillment support using methods, destinations, and groups. - """ - - -class FulfillmentOption( - RootModel[fulfillment_option_req.FulfillmentOptionRequest] -): - root: fulfillment_option_req.FulfillmentOptionRequest - - -class FulfillmentGroup( - RootModel[fulfillment_group_create_req.FulfillmentGroupCreateRequest] -): - root: fulfillment_group_create_req.FulfillmentGroupCreateRequest - - -class FulfillmentAvailableMethod( - RootModel[fulfillment_available_method_req.FulfillmentAvailableMethodRequest] -): - root: fulfillment_available_method_req.FulfillmentAvailableMethodRequest - - -class FulfillmentMethod( - RootModel[fulfillment_method_create_req.FulfillmentMethodCreateRequest] -): - root: fulfillment_method_create_req.FulfillmentMethodCreateRequest - - -class Fulfillment(RootModel[fulfillment_req.FulfillmentRequest]): - root: fulfillment_req.FulfillmentRequest - - -class Checkout(CheckoutCreateRequest): - """Checkout extended with hierarchical fulfillment.""" - - model_config = ConfigDict( - extra="allow", - ) - fulfillment: Fulfillment | None = None - """ - Fulfillment details. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/fulfillment_resp.py b/src/ucp_sdk/models/schemas/shopping/fulfillment_resp.py deleted file mode 100644 index ff445dc..0000000 --- a/src/ucp_sdk/models/schemas/shopping/fulfillment_resp.py +++ /dev/null @@ -1,79 +0,0 @@ -# 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 ConfigDict, Field, RootModel -from .types import ( - fulfillment_available_method_resp, - fulfillment_group_resp, - fulfillment_method_resp, - fulfillment_option_resp, - fulfillment_resp, -) -from .checkout_resp import CheckoutResponse - - -class FulfillmentExtensionResponse(RootModel[Any]): - root: Any = Field(..., title="Fulfillment Extension Response") - """ - Extends Checkout with fulfillment support using methods, destinations, and groups. - """ - - -class FulfillmentAvailableMethod( - RootModel[ - fulfillment_available_method_resp.FulfillmentAvailableMethodResponse - ] -): - root: fulfillment_available_method_resp.FulfillmentAvailableMethodResponse - - -class FulfillmentOption( - RootModel[fulfillment_option_resp.FulfillmentOptionResponse] -): - root: fulfillment_option_resp.FulfillmentOptionResponse - - -class FulfillmentGroup( - RootModel[fulfillment_group_resp.FulfillmentGroupResponse] -): - root: fulfillment_group_resp.FulfillmentGroupResponse - - -class FulfillmentMethod( - RootModel[fulfillment_method_resp.FulfillmentMethodResponse] -): - root: fulfillment_method_resp.FulfillmentMethodResponse - - -class Fulfillment(RootModel[fulfillment_resp.FulfillmentResponse]): - root: fulfillment_resp.FulfillmentResponse - - -class Checkout(CheckoutResponse): - """Checkout extended with hierarchical fulfillment.""" - - model_config = ConfigDict( - extra="allow", - ) - fulfillment: Fulfillment | None = None - """ - Fulfillment details. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/fulfillment_update_req.py b/src/ucp_sdk/models/schemas/shopping/fulfillment_update_req.py deleted file mode 100644 index 01d1608..0000000 --- a/src/ucp_sdk/models/schemas/shopping/fulfillment_update_req.py +++ /dev/null @@ -1,77 +0,0 @@ -# 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 ConfigDict, Field, RootModel -from .types import ( - fulfillment_available_method_req, - fulfillment_group_update_req, - fulfillment_method_update_req, - fulfillment_option_req, - fulfillment_req, -) -from .checkout_update_req import CheckoutUpdateRequest - - -class FulfillmentExtensionUpdateRequest(RootModel[Any]): - root: Any = Field(..., title="Fulfillment Extension Update Request") - """ - Extends Checkout with fulfillment support using methods, destinations, and groups. - """ - - -class FulfillmentOption( - RootModel[fulfillment_option_req.FulfillmentOptionRequest] -): - root: fulfillment_option_req.FulfillmentOptionRequest - - -class FulfillmentGroup( - RootModel[fulfillment_group_update_req.FulfillmentGroupUpdateRequest] -): - root: fulfillment_group_update_req.FulfillmentGroupUpdateRequest - - -class FulfillmentAvailableMethod( - RootModel[fulfillment_available_method_req.FulfillmentAvailableMethodRequest] -): - root: fulfillment_available_method_req.FulfillmentAvailableMethodRequest - - -class FulfillmentMethod( - RootModel[fulfillment_method_update_req.FulfillmentMethodUpdateRequest] -): - root: fulfillment_method_update_req.FulfillmentMethodUpdateRequest - - -class Fulfillment(RootModel[fulfillment_req.FulfillmentRequest]): - root: fulfillment_req.FulfillmentRequest - - -class Checkout(CheckoutUpdateRequest): - """Checkout extended with hierarchical fulfillment.""" - - model_config = ConfigDict( - extra="allow", - ) - fulfillment: Fulfillment | None = None - """ - Fulfillment details. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/order.py b/src/ucp_sdk/models/schemas/shopping/order.py index eeed457..645cd05 100644 --- a/src/ucp_sdk/models/schemas/shopping/order.py +++ b/src/ucp_sdk/models/schemas/shopping/order.py @@ -19,76 +19,77 @@ from __future__ import annotations from pydantic import AnyUrl, BaseModel, ConfigDict -from .types import ( - adjustment, - expectation, - fulfillment_event, - order_line_item, - total_resp, -) -from ..._internal import ResponseOrder +from .. import ucp as ucp_1 +from .types import adjustment, expectation, fulfillment_event, order_line_item, total -class PlatformConfig(BaseModel): - """Platform's order capability configuration.""" - model_config = ConfigDict( - extra="allow", - ) - webhook_url: AnyUrl - """ +class PlatformSchema(BaseModel): + """ + Platform's order capability configuration. + """ + + model_config = ConfigDict( + extra="allow", + ) + webhook_url: AnyUrl + """ URL where merchant sends order lifecycle events (webhooks). """ class Fulfillment(BaseModel): - """Fulfillment data: buyer expectations and what actually happened.""" + """ + Fulfillment data: buyer expectations and what actually happened. + """ - model_config = ConfigDict( - extra="allow", - ) - expectations: list[expectation.Expectation] | None = None - """ + model_config = ConfigDict( + extra="allow", + ) + expectations: list[expectation.Expectation] | None = None + """ Buyer-facing groups representing when/how items will be delivered. Can be split, merged, or adjusted post-order. """ - events: list[fulfillment_event.FulfillmentEvent] | None = None - """ + events: list[fulfillment_event.FulfillmentEvent] | None = None + """ Append-only event log of actual shipments. Each event references line items by ID. """ class Order(BaseModel): - """Order schema with immutable line items, buyer-facing fulfillment expectations, and append-only event logs.""" + """ + Order schema with immutable line items, buyer-facing fulfillment expectations, and append-only event logs. + """ - model_config = ConfigDict( - extra="allow", - ) - ucp: ResponseOrder - id: str - """ + model_config = ConfigDict( + extra="allow", + ) + ucp: ucp_1.ResponseOrderSchema + id: str + """ Unique order identifier. """ - checkout_id: str - """ + checkout_id: str + """ Associated checkout ID for reconciliation. """ - permalink_url: AnyUrl - """ + permalink_url: AnyUrl + """ Permalink to access the order on merchant site. """ - line_items: list[order_line_item.OrderLineItem] - """ + line_items: list[order_line_item.OrderLineItem] + """ Immutable line items — source of truth for what was ordered. """ - fulfillment: Fulfillment - """ + fulfillment: Fulfillment + """ Fulfillment data: buyer expectations and what actually happened. """ - adjustments: list[adjustment.Adjustment] | None = None - """ + adjustments: list[adjustment.Adjustment] | None = None + """ Append-only event log of money movements (refunds, returns, credits, disputes, cancellations, etc.) that exist independently of fulfillment. """ - totals: list[total_resp.TotalResponse] - """ + totals: list[total.Total] + """ Different totals for the order. """ diff --git a/src/ucp_sdk/models/schemas/shopping/payment_update_req.py b/src/ucp_sdk/models/schemas/shopping/payment.py similarity index 69% rename from src/ucp_sdk/models/schemas/shopping/payment_update_req.py rename to src/ucp_sdk/models/schemas/shopping/payment.py index 083d0a7..a4f5d78 100644 --- a/src/ucp_sdk/models/schemas/shopping/payment_update_req.py +++ b/src/ucp_sdk/models/schemas/shopping/payment.py @@ -19,20 +19,19 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict + from .types import payment_instrument -class PaymentUpdateRequest(BaseModel): - """Payment configuration containing handlers.""" +class Payment(BaseModel): + """ + Payment configuration containing handlers. + """ - model_config = ConfigDict( - extra="allow", - ) - selected_instrument_id: str | None = None - """ - The id of the currently selected payment instrument from the instruments array. Set by the agent when submitting payment, and echoed back by the merchant in finalized state. + model_config = ConfigDict( + extra="allow", + ) + instruments: list[payment_instrument.SelectedPaymentInstrument] | None = None """ - instruments: list[payment_instrument.PaymentInstrument] | None = None - """ The payment instruments available for this payment. Each instrument is associated with a specific handler via the handler_id field. Handlers can extend the base payment_instrument schema to add handler-specific fields. """ diff --git a/src/ucp_sdk/models/schemas/shopping/payment_create_req.py b/src/ucp_sdk/models/schemas/shopping/payment_create_req.py deleted file mode 100644 index 1b5359d..0000000 --- a/src/ucp_sdk/models/schemas/shopping/payment_create_req.py +++ /dev/null @@ -1,38 +0,0 @@ -# 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 .types import payment_instrument - - -class PaymentCreateRequest(BaseModel): - """Payment configuration containing handlers.""" - - model_config = ConfigDict( - extra="allow", - ) - selected_instrument_id: str | None = None - """ - The id of the currently selected payment instrument from the instruments array. Set by the agent when submitting payment, and echoed back by the merchant in finalized state. - """ - instruments: list[payment_instrument.PaymentInstrument] | None = None - """ - The payment instruments available for this payment. Each instrument is associated with a specific handler via the handler_id field. Handlers can extend the base payment_instrument schema to add handler-specific fields. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/payment_data.py b/src/ucp_sdk/models/schemas/shopping/payment_data.py deleted file mode 100644 index 5a8a03d..0000000 --- a/src/ucp_sdk/models/schemas/shopping/payment_data.py +++ /dev/null @@ -1,31 +0,0 @@ -# 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 .types import payment_instrument - - -class PaymentData(BaseModel): - """The data that will used to submit payment to the merchant.""" - - model_config = ConfigDict( - extra="allow", - ) - payment_data: payment_instrument.PaymentInstrument diff --git a/src/ucp_sdk/models/schemas/shopping/payment_resp.py b/src/ucp_sdk/models/schemas/shopping/payment_resp.py deleted file mode 100644 index e8dda1d..0000000 --- a/src/ucp_sdk/models/schemas/shopping/payment_resp.py +++ /dev/null @@ -1,42 +0,0 @@ -# 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 .types import payment_handler_resp, payment_instrument - - -class PaymentResponse(BaseModel): - """Payment configuration containing handlers.""" - - model_config = ConfigDict( - extra="allow", - ) - handlers: list[payment_handler_resp.PaymentHandlerResponse] - """ - Processing configurations that define how payment instruments can be collected. Each handler specifies a tokenization or payment collection strategy. - """ - selected_instrument_id: str | None = None - """ - The id of the currently selected payment instrument from the instruments array. Set by the agent when submitting payment, and echoed back by the merchant in finalized state. - """ - instruments: list[payment_instrument.PaymentInstrument] | None = None - """ - The payment instruments available for this payment. Each instrument is associated with a specific handler via the handler_id field. Handlers can extend the base payment_instrument schema to add handler-specific fields. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/account_info.py b/src/ucp_sdk/models/schemas/shopping/types/account_info.py index f06f00f..aeb2665 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/account_info.py +++ b/src/ucp_sdk/models/schemas/shopping/types/account_info.py @@ -22,12 +22,14 @@ class PaymentAccountInfo(BaseModel): - """Non-sensitive backend identifiers for linking.""" + """ + Non-sensitive backend identifiers for linking. + """ - model_config = ConfigDict( - extra="allow", - ) - payment_account_reference: str | None = None - """ + model_config = ConfigDict( + extra="allow", + ) + payment_account_reference: str | None = None + """ EMVCo PAR. A unique identifier linking a payment card to a specific account, enabling tracking across tokens (Apple Pay, physical card, etc). """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/adjustment.py b/src/ucp_sdk/models/schemas/shopping/types/adjustment.py index 17719e6..f28c0ea 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/adjustment.py +++ b/src/ucp_sdk/models/schemas/shopping/types/adjustment.py @@ -18,55 +18,58 @@ from __future__ import annotations -from pydantic import AwareDatetime, BaseModel, ConfigDict, Field from typing import Literal +from pydantic import AwareDatetime, BaseModel, ConfigDict, Field + class LineItem(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - id: str - """ + model_config = ConfigDict( + extra="allow", + ) + id: str + """ Line item ID reference. """ - quantity: int = Field(..., ge=1) - """ + quantity: int = Field(..., ge=1) + """ Quantity affected by this adjustment. """ class Adjustment(BaseModel): - """Append-only event that exists independently of fulfillment. Typically represents money movements but can be any post-order change. Polymorphic type that can optionally reference line items.""" + """ + Append-only event that exists independently of fulfillment. Typically represents money movements but can be any post-order change. Polymorphic type that can optionally reference line items. + """ - model_config = ConfigDict( - extra="allow", - ) - id: str - """ + model_config = ConfigDict( + extra="allow", + ) + id: str + """ Adjustment event identifier. """ - type: str - """ + type: str + """ Type of adjustment (open string). Typically money-related like: refund, return, credit, price_adjustment, dispute, cancellation. Can be any value that makes sense for the merchant's business. """ - occurred_at: AwareDatetime - """ + occurred_at: AwareDatetime + """ RFC 3339 timestamp when this adjustment occurred. """ - status: Literal["pending", "completed", "failed"] - """ + status: Literal["pending", "completed", "failed"] + """ Adjustment status. """ - line_items: list[LineItem] | None = None - """ + line_items: list[LineItem] | None = None + """ Which line items and quantities are affected (optional). """ - amount: int | None = None - """ + amount: int | None = None + """ Amount in minor units (cents) for refunds, credits, price adjustments (optional). """ - description: str | None = None - """ + description: str | None = None + """ Human-readable reason or description (e.g., 'Defective item', 'Customer requested'). """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/binding.py b/src/ucp_sdk/models/schemas/shopping/types/binding.py index c4971ca..16f6961 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/binding.py +++ b/src/ucp_sdk/models/schemas/shopping/types/binding.py @@ -19,20 +19,23 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict + from . import payment_identity class Binding(BaseModel): - """Binds a token to a specific checkout session and participant. Prevents token reuse across different checkouts or participants.""" + """ + Binds a token to a specific checkout session and participant. Prevents token reuse across different checkouts or participants. + """ - model_config = ConfigDict( - extra="allow", - ) - checkout_id: str - """ + model_config = ConfigDict( + extra="allow", + ) + checkout_id: str + """ The checkout session identifier this token is bound to. """ - identity: payment_identity.PaymentIdentity | None = None - """ + identity: payment_identity.PaymentIdentity | None = None + """ 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. """ 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 new file mode 100644 index 0000000..2786056 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/business_fulfillment_config.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 BaseModel, ConfigDict + + +class AllowsMultiDestination(BaseModel): + """ + Permits multiple destinations per method type. + """ + + model_config = ConfigDict( + extra="allow", + ) + shipping: bool | None = None + """ + Multiple shipping destinations allowed. + """ + pickup: bool | None = None + """ + Multiple pickup locations allowed. + """ + + +class BusinessFulfillmentConfig(BaseModel): + """ + Business's fulfillment configuration. + """ + + model_config = ConfigDict( + extra="allow", + ) + allows_multi_destination: AllowsMultiDestination | None = None + """ + Permits multiple destinations per method type. + """ + allows_method_combinations: list[list[Literal["shipping", "pickup"]]] | None = None + """ + Allowed method type combinations. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/buyer.py b/src/ucp_sdk/models/schemas/shopping/types/buyer.py index c93c729..ff8ef6a 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/buyer.py +++ b/src/ucp_sdk/models/schemas/shopping/types/buyer.py @@ -22,26 +22,22 @@ class Buyer(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - first_name: str | None = None - """ + model_config = ConfigDict( + extra="allow", + ) + first_name: str | None = None + """ First name of the buyer. """ - last_name: str | None = None - """ + last_name: str | None = None + """ Last name of the buyer. """ - full_name: str | None = None - """ - Optional, buyer's full name (if first_name or last_name fields are present they take precedence). + email: str | None = None """ - email: str | None = None - """ Email of the buyer. """ - phone_number: str | None = None - """ + phone_number: str | None = None + """ E.164 standard. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/card_credential.py b/src/ucp_sdk/models/schemas/shopping/types/card_credential.py index 40854dc..43b231a 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/card_credential.py +++ b/src/ucp_sdk/models/schemas/shopping/types/card_credential.py @@ -19,48 +19,53 @@ from __future__ import annotations from typing import Literal -from pydantic import BaseModel, ConfigDict, Field +from pydantic import ConfigDict, Field -class CardCredential(BaseModel): - """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.""" +from .payment_credential import PaymentCredential - model_config = ConfigDict( - extra="allow", - ) - type: Literal["card"] - """ + +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. + """ + + model_config = ConfigDict( + extra="allow", + ) + type: Literal["card"] + """ The credential type identifier for card credentials. """ - card_number_type: Literal["fpan", "network_token", "dpan"] - """ + 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. """ - number: str | None = Field(None, examples=["4242424242424242"]) - """ + number: str | None = Field(None, examples=["4242424242424242"]) + """ Card number. """ - expiry_month: int | None = None - """ + expiry_month: int | None = None + """ The month of the card's expiration date (1-12). """ - expiry_year: int | None = None - """ + expiry_year: int | None = None + """ The year of the card's expiration date. """ - name: str | None = Field(None, examples=["Jane Doe"]) - """ + name: str | None = Field(None, examples=["Jane Doe"]) + """ Cardholder name. """ - cvc: str | None = Field(None, examples=["223"], max_length=4) - """ + cvc: str | None = Field(None, examples=["223"], max_length=4) + """ Card CVC number. """ - cryptogram: str | None = Field(None, examples=["gXc5UCLnM6ckD7pjM1TdPA=="]) - """ + cryptogram: str | None = Field(None, examples=["gXc5UCLnM6ckD7pjM1TdPA=="]) + """ Cryptogram provided with network tokens. """ - eci_value: str | None = Field(None, examples=["07"]) - """ + eci_value: str | None = Field(None, examples=["07"]) + """ Electronic Commerce Indicator / Security Level Indicator provided with network tokens. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/card_payment_instrument.py b/src/ucp_sdk/models/schemas/shopping/types/card_payment_instrument.py index f57d38e..e5cffa1 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/card_payment_instrument.py +++ b/src/ucp_sdk/models/schemas/shopping/types/card_payment_instrument.py @@ -19,41 +19,59 @@ from __future__ import annotations from typing import Literal -from pydantic import AnyUrl, ConfigDict -from .payment_instrument_base import PaymentInstrumentBase +from pydantic import AnyUrl, BaseModel, ConfigDict -class CardPaymentInstrument(PaymentInstrumentBase): - """A basic card payment instrument with visible card details. Can be inherited by a handler's instrument schema to define handler-specific display details or more complex credential structures.""" +from .payment_instrument import PaymentInstrument - model_config = ConfigDict( - extra="allow", - ) - type: Literal["card"] - """ - Indicates this is a card payment instrument. + +class Display(BaseModel): + """ + Display information for this card payment instrument. + """ + + model_config = ConfigDict( + extra="allow", + ) + brand: str | None = None """ - brand: str - """ The card brand/network (e.g., visa, mastercard, amex). """ - last_digits: str - """ + last_digits: str | None = None + """ Last 4 digits of the card number. """ - expiry_month: int | None = None - """ + expiry_month: int | None = None + """ The month of the card's expiration date (1-12). """ - expiry_year: int | None = None - """ + expiry_year: int | None = None + """ The year of the card's expiration date. """ - rich_text_description: str | None = None - """ + description: str | None = None + """ An optional rich text description of the card to display to the user (e.g., 'Visa ending in 1234, expires 12/2025'). """ - rich_card_art: AnyUrl | None = None - """ + card_art: AnyUrl | None = None + """ An optional URI to a rich image representing the card (e.g., card art provided by the issuer). """ + + +class CardPaymentInstrument(PaymentInstrument): + """ + A basic card payment instrument with visible card details. Can be inherited by a handler's instrument schema to define handler-specific display details or more complex credential structures. + """ + + model_config = ConfigDict( + extra="allow", + ) + type: Literal["card"] + """ + Indicates this is a card payment instrument. + """ + display: Display | None = None + """ + Display information for this card payment instrument. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/context.py b/src/ucp_sdk/models/schemas/shopping/types/context.py new file mode 100644 index 0000000..cf73cf1 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/context.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 + + +class Context(BaseModel): + """ + Provisional buyer signals for relevance and localization: product availability, pricing, currency, tax, shipping, payment methods, and eligibility (e.g., student or affiliation discounts). Businesses SHOULD use these values when authoritative data (e.g., address) is absent, and MAY ignore unsupported values without returning errors. 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. Platforms SHOULD progressively enhance context throughout the buyer journey. + """ + + model_config = ConfigDict( + extra="allow", + ) + address_country: str | None = None + """ + 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. + """ + address_region: 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. + """ + postal_code: str | None = None + """ + The postal code. For example, 94043. Optional hint for regional refinement—higher-resolution data (e.g., shipping address) supersedes this value. + """ + intent: str | None = None + """ + Background context describing buyer's intent (e.g., 'looking for a gift under $50', 'need something durable for outdoor use'). Informs relevance, recommendations, and personalization. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/expectation.py b/src/ucp_sdk/models/schemas/shopping/types/expectation.py index 28b85a5..03e8182 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/expectation.py +++ b/src/ucp_sdk/models/schemas/shopping/types/expectation.py @@ -18,52 +18,56 @@ from __future__ import annotations -from pydantic import BaseModel, ConfigDict, Field from typing import Literal + +from pydantic import BaseModel, ConfigDict, Field + from . import postal_address class LineItem(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - id: str - """ + model_config = ConfigDict( + extra="allow", + ) + id: str + """ Line item ID reference. """ - quantity: int = Field(..., ge=1) - """ + quantity: int = Field(..., ge=1) + """ Quantity of this item in this expectation. """ class Expectation(BaseModel): - """Buyer-facing fulfillment expectation representing logical groupings of items (e.g., 'package'). Can be split, merged, or adjusted post-order to set buyer expectations for when/how items arrive.""" + """ + Buyer-facing fulfillment expectation representing logical groupings of items (e.g., 'package'). Can be split, merged, or adjusted post-order to set buyer expectations for when/how items arrive. + """ - model_config = ConfigDict( - extra="allow", - ) - id: str - """ + model_config = ConfigDict( + extra="allow", + ) + id: str + """ Expectation identifier. """ - line_items: list[LineItem] - """ + line_items: list[LineItem] + """ Which line items and quantities are in this expectation. """ - method_type: Literal["shipping", "pickup", "digital"] - """ + method_type: Literal["shipping", "pickup", "digital"] + """ Delivery method type (shipping, pickup, digital). """ - destination: postal_address.PostalAddress - """ + destination: postal_address.PostalAddress + """ Delivery destination address. """ - description: str | None = None - """ + description: str | None = None + """ Human-readable delivery description (e.g., 'Arrives in 5-8 business days'). """ - fulfillable_on: str | None = None - """ + fulfillable_on: str | None = None + """ When this expectation can be fulfilled: 'now' or ISO 8601 timestamp for future date (backorder, pre-order). """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_resp.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment.py similarity index 65% rename from src/ucp_sdk/models/schemas/shopping/types/fulfillment_resp.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment.py index 2c23ea1..5603b07 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_resp.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment.py @@ -19,23 +19,23 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict -from . import fulfillment_available_method_resp, fulfillment_method_resp +from . import fulfillment_available_method, fulfillment_method -class FulfillmentResponse(BaseModel): - """Container for fulfillment methods and availability.""" - model_config = ConfigDict( - extra="allow", - ) - methods: list[fulfillment_method_resp.FulfillmentMethodResponse] | None = None - """ +class Fulfillment(BaseModel): + """ + Container for fulfillment methods and availability. + """ + + model_config = ConfigDict( + extra="allow", + ) + methods: list[fulfillment_method.FulfillmentMethod] | None = None + """ Fulfillment methods for cart items. """ - available_methods: ( - list[fulfillment_available_method_resp.FulfillmentAvailableMethodResponse] - | None - ) = None - """ + available_methods: list[fulfillment_available_method.FulfillmentAvailableMethod] | None = None + """ Inventory availability hints. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_resp.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method.py similarity index 75% rename from src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_resp.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method.py index 9181950..4c9a1bb 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_resp.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method.py @@ -19,28 +19,31 @@ from __future__ import annotations from typing import Literal + from pydantic import BaseModel, ConfigDict -class FulfillmentAvailableMethodResponse(BaseModel): - """Inventory availability hint for a fulfillment method type.""" +class FulfillmentAvailableMethod(BaseModel): + """ + Inventory availability hint for a fulfillment method type. + """ - model_config = ConfigDict( - extra="allow", - ) - type: Literal["shipping", "pickup"] - """ + model_config = ConfigDict( + extra="allow", + ) + type: Literal["shipping", "pickup"] + """ Fulfillment method type this availability applies to. """ - line_item_ids: list[str] - """ + line_item_ids: list[str] + """ Line items available for this fulfillment method. """ - fulfillable_on: str | None = None - """ + fulfillable_on: str | None = None + """ 'now' for immediate availability, or ISO 8601 date for future (preorders, transfers). """ - description: str | None = None - """ + description: str | None = None + """ Human-readable availability info (e.g., 'Available for pickup at Downtown Store today'). """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_req.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_req.py deleted file mode 100644 index 6bb4ef5..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_req.py +++ /dev/null @@ -1,29 +0,0 @@ -# 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 FulfillmentAvailableMethodRequest(BaseModel): - """Inventory availability hint for a fulfillment method type.""" - - model_config = ConfigDict( - extra="allow", - ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_req.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination.py similarity index 65% rename from src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_req.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination.py index e8d45eb..3f4351a 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination.py @@ -19,19 +19,14 @@ from __future__ import annotations from pydantic import Field, RootModel -from . import retail_location_req, shipping_destination_req +from . import retail_location, shipping_destination -class FulfillmentDestinationRequest( - RootModel[ - shipping_destination_req.ShippingDestinationRequest - | retail_location_req.RetailLocationRequest - ] -): - root: ( - shipping_destination_req.ShippingDestinationRequest - | retail_location_req.RetailLocationRequest - ) = Field(..., title="Fulfillment Destination Request") - """ + +class FulfillmentDestination(RootModel[shipping_destination.ShippingDestination | retail_location.RetailLocation]): + root: shipping_destination.ShippingDestination | retail_location.RetailLocation = Field( + ..., title="Fulfillment Destination" + ) + """ A destination for fulfillment. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_resp.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_resp.py deleted file mode 100644 index 91ae5fb..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_resp.py +++ /dev/null @@ -1,37 +0,0 @@ -# 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 Field, RootModel -from . import retail_location_resp, shipping_destination_resp - - -class FulfillmentDestinationResponse( - RootModel[ - shipping_destination_resp.ShippingDestinationResponse - | retail_location_resp.RetailLocationResponse - ] -): - root: ( - shipping_destination_resp.ShippingDestinationResponse - | retail_location_resp.RetailLocationResponse - ) = Field(..., title="Fulfillment Destination Response") - """ - A destination for fulfillment. - """ 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 5711111..68e09c6 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_event.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_event.py @@ -22,54 +22,56 @@ class LineItem(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - id: str - """ + model_config = ConfigDict( + extra="allow", + ) + id: str + """ Line item ID reference. """ - quantity: int = Field(..., ge=1) - """ + quantity: int = Field(..., ge=1) + """ Quantity fulfilled in this event. """ class FulfillmentEvent(BaseModel): - """Append-only fulfillment event representing an actual shipment. References line items by ID.""" + """ + Append-only fulfillment event representing an actual shipment. References line items by ID. + """ - model_config = ConfigDict( - extra="allow", - ) - id: str - """ + model_config = ConfigDict( + extra="allow", + ) + id: str + """ Fulfillment event identifier. """ - occurred_at: AwareDatetime - """ + occurred_at: AwareDatetime + """ RFC 3339 timestamp when this fulfillment event occurred. """ - type: str - """ + type: str + """ Fulfillment event type. Common values include: processing (preparing to ship), shipped (handed to carrier), in_transit (in delivery network), delivered (received by buyer), failed_attempt (delivery attempt failed), canceled (fulfillment canceled), undeliverable (cannot be delivered), returned_to_sender (returned to merchant). """ - line_items: list[LineItem] - """ + line_items: list[LineItem] + """ Which line items and quantities are fulfilled in this event. """ - tracking_number: str | None = None - """ + tracking_number: str | None = None + """ Carrier tracking number (required if type != processing). """ - tracking_url: AnyUrl | None = None - """ + tracking_url: AnyUrl | None = None + """ URL to track this shipment (required if type != processing). """ - carrier: str | None = None - """ + carrier: str | None = None + """ Carrier name (e.g., 'FedEx', 'USPS'). """ - description: str | None = None - """ + description: str | None = None + """ Human-readable description of the shipment status or delivery information (e.g., 'Delivered to front door', 'Out for delivery'). """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_resp.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group.py similarity index 70% rename from src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_resp.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_group.py index cf00987..b3646b8 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_resp.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group.py @@ -19,28 +19,31 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict -from . import fulfillment_option_resp +from . import fulfillment_option -class FulfillmentGroupResponse(BaseModel): - """A merchant-generated package/group of line items with fulfillment options.""" - model_config = ConfigDict( - extra="allow", - ) - id: str - """ +class FulfillmentGroup(BaseModel): + """ + A merchant-generated package/group of line items with fulfillment options. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ Group identifier for referencing merchant-generated groups in updates. """ - line_item_ids: list[str] - """ + line_item_ids: list[str] + """ Line item IDs included in this group/package. """ - options: list[fulfillment_option_resp.FulfillmentOptionResponse] | None = None - """ + options: list[fulfillment_option.FulfillmentOption] | None = None + """ Available fulfillment options for this group. """ - selected_option_id: str | None = None - """ + selected_option_id: str | None = None + """ ID of the selected fulfillment option for this group. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_create_req.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_create_req.py deleted file mode 100644 index 171cf2b..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_create_req.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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 FulfillmentGroupCreateRequest(BaseModel): - """A merchant-generated package/group of line items with fulfillment options.""" - - model_config = ConfigDict( - extra="allow", - ) - selected_option_id: str | None = None - """ - ID of the selected fulfillment option for this group. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_resp.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method.py similarity index 66% rename from src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_resp.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_method.py index a593d84..f6608d0 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_resp.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method.py @@ -19,39 +19,41 @@ from __future__ import annotations from typing import Literal + from pydantic import BaseModel, ConfigDict -from . import fulfillment_destination_resp, fulfillment_group_resp +from . import fulfillment_destination, fulfillment_group -class FulfillmentMethodResponse(BaseModel): - """A fulfillment method (shipping or pickup) with destinations and groups.""" - model_config = ConfigDict( - extra="allow", - ) - id: str - """ +class FulfillmentMethod(BaseModel): + """ + A fulfillment method (shipping or pickup) with destinations and groups. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ Unique fulfillment method identifier. """ - type: Literal["shipping", "pickup"] - """ + type: Literal["shipping", "pickup"] + """ Fulfillment method type. """ - line_item_ids: list[str] - """ + line_item_ids: list[str] + """ Line item IDs fulfilled via this method. """ - destinations: ( - list[fulfillment_destination_resp.FulfillmentDestinationResponse] | None - ) = None - """ + destinations: list[fulfillment_destination.FulfillmentDestination] | None = None + """ Available destinations. For shipping: addresses. For pickup: retail locations. """ - selected_destination_id: str | None = None - """ + selected_destination_id: str | None = None + """ ID of the selected destination. """ - groups: list[fulfillment_group_resp.FulfillmentGroupResponse] | None = None - """ + groups: list[fulfillment_group.FulfillmentGroup] | None = None + """ Fulfillment groups for selecting options. Agent sets selected_option_id on groups to choose shipping method. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_create_req.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_create_req.py deleted file mode 100644 index 3c2e61d..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_create_req.py +++ /dev/null @@ -1,55 +0,0 @@ -# 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 fulfillment_destination_req, fulfillment_group_create_req - - -class FulfillmentMethodCreateRequest(BaseModel): - """A fulfillment method (shipping or pickup) 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_req.FulfillmentDestinationRequest] | None - ) = None - """ - Available destinations. For shipping: addresses. For pickup: retail locations. - """ - selected_destination_id: str | None = None - """ - ID of the selected destination. - """ - groups: ( - list[fulfillment_group_create_req.FulfillmentGroupCreateRequest] | None - ) = None - """ - Fulfillment groups for selecting options. Agent sets selected_option_id on groups to choose shipping method. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_req.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_req.py deleted file mode 100644 index 253f03f..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_req.py +++ /dev/null @@ -1,54 +0,0 @@ -# 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 fulfillment_destination_req, fulfillment_group_update_req - - -class FulfillmentMethodUpdateRequest(BaseModel): - """A fulfillment method (shipping or pickup) with destinations and groups.""" - - model_config = ConfigDict( - extra="allow", - ) - id: str - """ - Unique fulfillment method identifier. - """ - line_item_ids: list[str] - """ - Line item IDs fulfilled via this method. - """ - destinations: ( - list[fulfillment_destination_req.FulfillmentDestinationRequest] | None - ) = None - """ - Available destinations. For shipping: addresses. For pickup: retail locations. - """ - selected_destination_id: str | None = None - """ - ID of the selected destination. - """ - groups: ( - list[fulfillment_group_update_req.FulfillmentGroupUpdateRequest] | None - ) = None - """ - Fulfillment groups for selecting options. Agent sets selected_option_id on groups to choose shipping method. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_resp.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option.py similarity index 68% rename from src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_resp.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_option.py index 3ba75eb..93b3bf8 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_resp.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option.py @@ -19,40 +19,43 @@ from __future__ import annotations from pydantic import AwareDatetime, BaseModel, ConfigDict -from . import total_resp +from . import total -class FulfillmentOptionResponse(BaseModel): - """A fulfillment option within a group (e.g., Standard Shipping $5, Express $15).""" - model_config = ConfigDict( - extra="allow", - ) - id: str - """ +class FulfillmentOption(BaseModel): + """ + A fulfillment option within a group (e.g., Standard Shipping $5, Express $15). + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ Unique fulfillment option identifier. """ - title: str - """ + title: str + """ Short label (e.g., 'Express Shipping', 'Curbside Pickup'). """ - description: str | None = None - """ + description: str | None = None + """ Complete context for buyer decision (e.g., 'Arrives Dec 12-15 via FedEx'). """ - carrier: str | None = None - """ + carrier: str | None = None + """ Carrier name (for shipping). """ - earliest_fulfillment_time: AwareDatetime | None = None - """ + earliest_fulfillment_time: AwareDatetime | None = None + """ Earliest fulfillment date. """ - latest_fulfillment_time: AwareDatetime | None = None - """ + latest_fulfillment_time: AwareDatetime | None = None + """ Latest fulfillment date. """ - totals: list[total_resp.TotalResponse] - """ + totals: list[total.Total] + """ Fulfillment option totals breakdown. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_req.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_req.py deleted file mode 100644 index d33445e..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_req.py +++ /dev/null @@ -1,29 +0,0 @@ -# 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 FulfillmentOptionRequest(BaseModel): - """A fulfillment option within a group (e.g., Standard Shipping $5, Express $15).""" - - model_config = ConfigDict( - extra="allow", - ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_req.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_req.py deleted file mode 100644 index 560ce75..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_req.py +++ /dev/null @@ -1,36 +0,0 @@ -# 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 fulfillment_method_create_req - - -class FulfillmentRequest(BaseModel): - """Container for fulfillment methods and availability.""" - - model_config = ConfigDict( - extra="allow", - ) - methods: ( - list[fulfillment_method_create_req.FulfillmentMethodCreateRequest] | None - ) = None - """ - Fulfillment methods for cart items. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/item_resp.py b/src/ucp_sdk/models/schemas/shopping/types/item.py similarity index 69% rename from src/ucp_sdk/models/schemas/shopping/types/item_resp.py rename to src/ucp_sdk/models/schemas/shopping/types/item.py index 11b7afc..d942f0b 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/item_resp.py +++ b/src/ucp_sdk/models/schemas/shopping/types/item.py @@ -21,23 +21,23 @@ from pydantic import AnyUrl, BaseModel, ConfigDict, Field -class ItemResponse(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - id: str - """ - Should be recognized by both the Platform, and the Business. For Google it should match the id provided in the "id" field in the product feed. - """ - title: str - """ +class Item(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + 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. + """ + title: str + """ Product title. """ - price: int = Field(..., ge=0) - """ + price: int = Field(..., ge=0) + """ Unit price in minor (cents) currency units. """ - image_url: AnyUrl | None = None - """ + image_url: AnyUrl | None = None + """ Product image URI. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/item_create_req.py b/src/ucp_sdk/models/schemas/shopping/types/item_create_req.py deleted file mode 100644 index 19e0d07..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/item_create_req.py +++ /dev/null @@ -1,31 +0,0 @@ -# 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 ItemCreateRequest(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - id: str - """ - Should be recognized by both the Platform, and the Business. For Google it should match the id provided in the "id" field in the product feed. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/item_update_req.py b/src/ucp_sdk/models/schemas/shopping/types/item_update_req.py deleted file mode 100644 index 156fe97..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/item_update_req.py +++ /dev/null @@ -1,31 +0,0 @@ -# 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 ItemUpdateRequest(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - id: str - """ - Should be recognized by both the Platform, and the Business. For Google it should match the id provided in the "id" field in the product feed. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/line_item_resp.py b/src/ucp_sdk/models/schemas/shopping/types/line_item.py similarity index 70% rename from src/ucp_sdk/models/schemas/shopping/types/line_item_resp.py rename to src/ucp_sdk/models/schemas/shopping/types/line_item.py index 963d6e6..9f2befe 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/line_item_resp.py +++ b/src/ucp_sdk/models/schemas/shopping/types/line_item.py @@ -19,26 +19,30 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field -from . import item_resp, total_resp +from . import item as item_1 +from . import total -class LineItemResponse(BaseModel): - """Line item object. Expected to use the currency of the parent object.""" - model_config = ConfigDict( - extra="allow", - ) - id: str - item: item_resp.ItemResponse - quantity: int = Field(..., ge=1) - """ +class LineItem(BaseModel): + """ + Line item object. Expected to use the currency of the parent object. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + item: item_1.Item + quantity: int = Field(..., ge=1) + """ Quantity of the item being purchased. """ - totals: list[total_resp.TotalResponse] - """ + totals: list[total.Total] + """ Line item totals breakdown. """ - parent_id: str | None = None - """ + parent_id: str | None = None + """ Parent line item identifier for any nested structures. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/line_item_create_req.py b/src/ucp_sdk/models/schemas/shopping/types/line_item_create_req.py deleted file mode 100644 index 310c501..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/line_item_create_req.py +++ /dev/null @@ -1,35 +0,0 @@ -# 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 item_create_req - - -class LineItemCreateRequest(BaseModel): - """Line item object. Expected to use the currency of the parent object.""" - - model_config = ConfigDict( - extra="allow", - ) - item: item_create_req.ItemCreateRequest - quantity: int = Field(..., ge=1) - """ - Quantity of the item being purchased. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/line_item_update_req.py b/src/ucp_sdk/models/schemas/shopping/types/line_item_update_req.py deleted file mode 100644 index 5322178..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/line_item_update_req.py +++ /dev/null @@ -1,40 +0,0 @@ -# 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 item_update_req - - -class LineItemUpdateRequest(BaseModel): - """Line item object. Expected to use the currency of the parent object.""" - - model_config = ConfigDict( - extra="allow", - ) - id: str | None = None - item: item_update_req.ItemUpdateRequest - quantity: int = Field(..., ge=1) - """ - Quantity of the item being purchased. - """ - parent_id: str | None = None - """ - Parent line item identifier for any nested structures. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/link.py b/src/ucp_sdk/models/schemas/shopping/types/link.py index ada733f..c3cdd02 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/link.py +++ b/src/ucp_sdk/models/schemas/shopping/types/link.py @@ -22,18 +22,18 @@ class Link(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - type: str - """ + model_config = ConfigDict( + extra="allow", + ) + type: str + """ Type of link. Well-known values: `privacy_policy`, `terms_of_service`, `refund_policy`, `shipping_policy`, `faq`. Consumers SHOULD handle unknown values gracefully by displaying them using the `title` field or omitting the link. """ - url: AnyUrl - """ + url: AnyUrl + """ The actual URL pointing to the content to be displayed. """ - title: str | None = None - """ + title: str | None = None + """ Optional display text for the link. When provided, use this instead of generating from type. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/merchant_fulfillment_config.py b/src/ucp_sdk/models/schemas/shopping/types/merchant_fulfillment_config.py index 29456f5..48919c5 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/merchant_fulfillment_config.py +++ b/src/ucp_sdk/models/schemas/shopping/types/merchant_fulfillment_config.py @@ -18,39 +18,42 @@ from __future__ import annotations -from pydantic import BaseModel, ConfigDict from typing import Literal +from pydantic import BaseModel, ConfigDict + class AllowsMultiDestination(BaseModel): - """Permits multiple destinations per method type.""" + """ + Permits multiple destinations per method type. + """ - model_config = ConfigDict( - extra="allow", - ) - shipping: bool | None = None - """ + model_config = ConfigDict( + extra="allow", + ) + shipping: bool | None = None + """ Multiple shipping destinations allowed. """ - pickup: bool | None = None - """ + pickup: bool | None = None + """ Multiple pickup locations allowed. """ class MerchantFulfillmentConfig(BaseModel): - """Merchant's fulfillment configuration.""" + """ + Merchant's fulfillment configuration. + """ - model_config = ConfigDict( - extra="allow", - ) - allows_multi_destination: AllowsMultiDestination | None = None - """ + model_config = ConfigDict( + extra="allow", + ) + allows_multi_destination: AllowsMultiDestination | None = None + """ Permits multiple destinations per method type. """ - allows_method_combinations: ( - list[list[Literal["shipping", "pickup"]]] | None - ) = None - """ + allows_method_combinations: list[list[Literal["shipping", "pickup"]]] | None = None + """ Allowed method type combinations. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/message.py b/src/ucp_sdk/models/schemas/shopping/types/message.py index 452fdb7..cbf3c5f 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message.py +++ b/src/ucp_sdk/models/schemas/shopping/types/message.py @@ -19,21 +19,14 @@ from __future__ import annotations from pydantic import Field, RootModel + from . import message_error, message_info, message_warning -class Message( - RootModel[ - message_error.MessageError - | message_warning.MessageWarning - | message_info.MessageInfo - ] -): - root: ( - message_error.MessageError - | message_warning.MessageWarning - | message_info.MessageInfo - ) = Field(..., title="Message") - """ +class Message(RootModel[message_error.MessageError | message_warning.MessageWarning | message_info.MessageInfo]): + root: message_error.MessageError | message_warning.MessageWarning | message_info.MessageInfo = Field( + ..., title="Message" + ) + """ Container for error, warning, or info messages. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_error.py b/src/ucp_sdk/models/schemas/shopping/types/message_error.py index 2a1dffd..1dd3500 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message_error.py +++ b/src/ucp_sdk/models/schemas/shopping/types/message_error.py @@ -19,36 +19,35 @@ from __future__ import annotations from typing import Literal + from pydantic import BaseModel, ConfigDict class MessageError(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - type: Literal["error"] - """ + model_config = ConfigDict( + extra="allow", + ) + type: Literal["error"] + """ Message type discriminator. """ - code: str - """ + code: str + """ Error code. Possible values include: missing, invalid, out_of_stock, payment_declined, requires_sign_in, requires_3ds, requires_identity_linking. Freeform codes also allowed. """ - path: str | None = None - """ + 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_type: Literal["plain", "markdown"] | None = "plain" + """ Content format, default = plain. """ - content: str - """ + content: str + """ Human-readable message. """ - severity: Literal[ - "recoverable", "requires_buyer_input", "requires_buyer_review" - ] - """ + severity: Literal["recoverable", "requires_buyer_input", "requires_buyer_review"] + """ Declares who resolves this error. 'recoverable': agent can fix 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 (checkout complete). 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/shopping/types/message_info.py index 69f70f8..e0bcb31 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message_info.py +++ b/src/ucp_sdk/models/schemas/shopping/types/message_info.py @@ -19,30 +19,31 @@ from __future__ import annotations from typing import Literal + from pydantic import BaseModel, ConfigDict class MessageInfo(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - type: Literal["info"] - """ + model_config = ConfigDict( + extra="allow", + ) + type: Literal["info"] + """ Message type discriminator. """ - path: str | None = None - """ + path: str | None = None + """ RFC 9535 JSONPath to the component the message refers to. """ - code: str | None = None - """ + code: str | None = None + """ Info code for programmatic handling. """ - content_type: Literal["plain", "markdown"] | None = "plain" - """ + content_type: Literal["plain", "markdown"] | None = "plain" + """ Content format, default = plain. """ - content: str - """ + content: str + """ Human-readable message. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_warning.py b/src/ucp_sdk/models/schemas/shopping/types/message_warning.py index 564f391..f5eee46 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message_warning.py +++ b/src/ucp_sdk/models/schemas/shopping/types/message_warning.py @@ -19,30 +19,31 @@ from __future__ import annotations from typing import Literal + from pydantic import BaseModel, ConfigDict class MessageWarning(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - type: Literal["warning"] - """ + model_config = ConfigDict( + extra="allow", + ) + type: Literal["warning"] + """ Message type discriminator. """ - path: str | None = None - """ + path: str | None = None + """ JSONPath (RFC 9535) to related field (e.g., $.line_items[0]). """ - code: str - """ + code: str + """ Warning code. Machine-readable identifier for the warning type (e.g., final_sale, prop65, fulfillment_changed, age_restricted, etc.). """ - content: str - """ + content: str + """ Human-readable warning message that MUST be displayed. """ - content_type: Literal["plain", "markdown"] | None = "plain" - """ + content_type: Literal["plain", "markdown"] | None = "plain" + """ Content format, default = plain. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/order_confirmation.py b/src/ucp_sdk/models/schemas/shopping/types/order_confirmation.py index 2ddb26f..b17864a 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/order_confirmation.py +++ b/src/ucp_sdk/models/schemas/shopping/types/order_confirmation.py @@ -22,16 +22,18 @@ class OrderConfirmation(BaseModel): - """Order details available at the time of checkout completion.""" + """ + Order details available at the time of checkout completion. + """ - model_config = ConfigDict( - extra="allow", - ) - id: str - """ + model_config = ConfigDict( + extra="allow", + ) + id: str + """ Unique order identifier. """ - permalink_url: AnyUrl - """ + permalink_url: AnyUrl + """ Permalink to access the order on merchant site. """ 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 841b19c..8f991bc 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 @@ -18,52 +18,57 @@ from __future__ import annotations -from pydantic import BaseModel, ConfigDict, Field from typing import Literal -from . import item_resp, total_resp + +from pydantic import BaseModel, ConfigDict, Field + +from . import item as item_1 +from . import total as total_1 class Quantity(BaseModel): - """Quantity tracking. Both total and fulfilled are derived from events.""" + """ + Quantity tracking. Both total and fulfilled are derived from events. + """ - model_config = ConfigDict( - extra="allow", - ) - total: int = Field(..., ge=0) - """ + model_config = ConfigDict( + extra="allow", + ) + total: int = Field(..., ge=0) + """ Current total quantity. """ - fulfilled: int = Field(..., ge=0) - """ + fulfilled: int = Field(..., ge=0) + """ Quantity fulfilled (sum from fulfillment events). """ class OrderLineItem(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - id: str - """ + model_config = ConfigDict( + extra="allow", + ) + id: str + """ Line item identifier. """ - item: item_resp.ItemResponse - """ + item: item_1.Item + """ Product data (id, title, price, image_url). """ - quantity: Quantity - """ + quantity: Quantity + """ Quantity tracking. Both total and fulfilled are derived from events. """ - totals: list[total_resp.TotalResponse] - """ + totals: list[total_1.Total] + """ Line item totals breakdown. """ - status: Literal["processing", "partial", "fulfilled"] - """ + status: Literal["processing", "partial", "fulfilled"] + """ Derived status: fulfilled if quantity.fulfilled == quantity.total, partial if quantity.fulfilled > 0, otherwise processing. """ - parent_id: str | None = None - """ + parent_id: str | None = None + """ Parent line item identifier for any nested structures. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/payment_credential.py b/src/ucp_sdk/models/schemas/shopping/types/payment_credential.py index 1e5b2c4..4a4e09d 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/payment_credential.py +++ b/src/ucp_sdk/models/schemas/shopping/types/payment_credential.py @@ -18,20 +18,18 @@ from __future__ import annotations -from pydantic import Field, RootModel -from . import card_credential, token_credential_resp +from pydantic import BaseModel, ConfigDict -class PaymentCredential( - RootModel[ - token_credential_resp.TokenCredentialResponse - | card_credential.CardCredential - ] -): - root: ( - token_credential_resp.TokenCredentialResponse - | card_credential.CardCredential - ) = Field(..., title="Payment Credential") - """ - Container for sensitive payment data. Use the specific schema matching the 'type' field. +class PaymentCredential(BaseModel): + """ + The base definition for any payment credential. Handlers define specific credential types. + """ + + model_config = ConfigDict( + extra="allow", + ) + type: str + """ + The credential type discriminator. Specific schemas will constrain this to a constant value. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/payment_handler_create_req.py b/src/ucp_sdk/models/schemas/shopping/types/payment_handler_create_req.py deleted file mode 100644 index e8526ef..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/payment_handler_create_req.py +++ /dev/null @@ -1,27 +0,0 @@ -# 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 PaymentHandlerCreateRequest(BaseModel): - model_config = ConfigDict( - extra="allow", - ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/payment_handler_resp.py b/src/ucp_sdk/models/schemas/shopping/types/payment_handler_resp.py deleted file mode 100644 index c254191..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/payment_handler_resp.py +++ /dev/null @@ -1,54 +0,0 @@ -# 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 typing import Any -from ...._internal import Version - - -class PaymentHandlerResponse(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - id: str - """ - The unique identifier for this handler instance within the payment.handlers. Used by payment instruments to reference which handler produced them. - """ - name: str - """ - The specification name using reverse-DNS format. For example, dev.ucp.delegate_payment. - """ - version: Version - """ - Handler version in YYYY-MM-DD format. - """ - spec: AnyUrl - """ - A URI pointing to the technical specification or schema that defines how this handler operates. - """ - config_schema: AnyUrl - """ - A URI pointing to a JSON Schema used to validate the structure of the config object. - """ - instrument_schemas: list[AnyUrl] - config: dict[str, Any] - """ - A dictionary containing provider-specific configuration details, such as merchant IDs, supported networks, or gateway credentials. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/payment_handler_update_req.py b/src/ucp_sdk/models/schemas/shopping/types/payment_handler_update_req.py deleted file mode 100644 index ce959e0..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/payment_handler_update_req.py +++ /dev/null @@ -1,27 +0,0 @@ -# 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 PaymentHandlerUpdateRequest(BaseModel): - model_config = ConfigDict( - extra="allow", - ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/payment_identity.py b/src/ucp_sdk/models/schemas/shopping/types/payment_identity.py index c61230f..b30f85d 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/payment_identity.py +++ b/src/ucp_sdk/models/schemas/shopping/types/payment_identity.py @@ -22,12 +22,14 @@ 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 who tokens should be bound to. + """ - model_config = ConfigDict( - extra="allow", - ) - access_token: str - """ + model_config = ConfigDict( + extra="allow", + ) + access_token: str + """ Unique identifier for this participant, obtained during onboarding with the tokenizer. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/payment_instrument.py b/src/ucp_sdk/models/schemas/shopping/types/payment_instrument.py index 449580f..e99d68f 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/payment_instrument.py +++ b/src/ucp_sdk/models/schemas/shopping/types/payment_instrument.py @@ -18,16 +18,43 @@ from __future__ import annotations -from pydantic import Field, RootModel -from . import card_payment_instrument - - -class PaymentInstrument( - RootModel[card_payment_instrument.CardPaymentInstrument] -): - root: card_payment_instrument.CardPaymentInstrument = Field( - ..., title="Payment Instrument" - ) - """ - Matches a specific instrument type based on validation logic. +from typing import Any + +from pydantic import BaseModel, ConfigDict, RootModel + +from . import payment_credential, postal_address + + +class SelectedPaymentInstrument(RootModel[Any]): + root: Any + + +class PaymentInstrument(BaseModel): + """ + The base definition for any payment instrument. It links the instrument to a specific payment handler. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + A unique identifier for this instrument instance, assigned by the platform. + """ + handler_id: str + """ + The unique identifier for the handler instance that produced this instrument. This corresponds to the 'id' field in the Payment Handler definition. + """ + type: str + """ + The broad category of the instrument (e.g., 'card', 'tokenized_card'). Specific schemas will constrain this to a constant value. + """ + billing_address: postal_address.PostalAddress | None = None + """ + The billing address associated with this payment method. + """ + credential: payment_credential.PaymentCredential | None = None + display: dict[str, Any] | None = None + """ + Display information for this payment instrument. Each payment instrument schema defines its specific display properties, as outlined by the payment handler. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/payment_instrument_base.py b/src/ucp_sdk/models/schemas/shopping/types/payment_instrument_base.py deleted file mode 100644 index 1bb01da..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/payment_instrument_base.py +++ /dev/null @@ -1,47 +0,0 @@ -# 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 payment_credential, postal_address - - -class PaymentInstrumentBase(BaseModel): - """The base definition for any payment instrument. It links the instrument to a specific Merchant configuration (handler_id) and defines common fields like billing address.""" - - model_config = ConfigDict( - extra="allow", - ) - id: str - """ - A unique identifier for this instrument instance, assigned by the Agent. Used to reference this specific instrument in the 'payment.selected_instrument_id' field. - """ - handler_id: str - """ - The unique identifier for the handler instance that produced this instrument. This corresponds to the 'id' field in the Payment Handler definition. - """ - type: str - """ - The broad category of the instrument (e.g., 'card', 'tokenized_card'). Specific schemas will constrain this to a constant value. - """ - billing_address: postal_address.PostalAddress | None = None - """ - The billing address associated with this payment method. - """ - credential: payment_credential.PaymentCredential | None = None diff --git a/src/ucp_sdk/models/schemas/shopping/types/platform_fulfillment_config.py b/src/ucp_sdk/models/schemas/shopping/types/platform_fulfillment_config.py index f3ce34a..5757821 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/platform_fulfillment_config.py +++ b/src/ucp_sdk/models/schemas/shopping/types/platform_fulfillment_config.py @@ -22,12 +22,14 @@ class PlatformFulfillmentConfig(BaseModel): - """Platform's fulfillment configuration.""" + """ + Platform's fulfillment configuration. + """ - model_config = ConfigDict( - extra="allow", - ) - supports_multi_group: bool | None = False - """ + model_config = ConfigDict( + extra="allow", + ) + supports_multi_group: bool | None = False + """ Enables multiple groups per method. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/postal_address.py b/src/ucp_sdk/models/schemas/shopping/types/postal_address.py index 309dfdd..7c57364 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/postal_address.py +++ b/src/ucp_sdk/models/schemas/shopping/types/postal_address.py @@ -22,46 +22,42 @@ class PostalAddress(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - extended_address: str | None = None - """ + model_config = ConfigDict( + extra="allow", + ) + extended_address: str | None = None + """ An address extension such as an apartment number, C/O or alternative name. """ - street_address: str | None = None - """ + street_address: str | None = None + """ The street address. """ - address_locality: str | None = None - """ + address_locality: str | None = None + """ The locality in which the street address is, and which is in the region. For example, Mountain View. """ - address_region: str | None = None - """ + address_region: str | None = None + """ The region in which the locality is, and which is in the country. Required for applicable countries (i.e. state in US, province in CA). For example, California or another appropriate first-level Administrative division. """ - address_country: str | None = None - """ + address_country: str | None = None + """ 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. """ - postal_code: str | None = None - """ + postal_code: str | None = None + """ The postal code. For example, 94043. """ - first_name: str | None = None - """ + first_name: str | None = None + """ Optional. First name of the contact associated with the address. """ - last_name: str | None = None - """ + last_name: str | None = None + """ Optional. Last name of the contact associated with the address. """ - full_name: str | None = None - """ - Optional. Full name of the contact associated with the address (if first_name or last_name fields are present they take precedence). + phone_number: str | None = None """ - phone_number: str | None = None - """ Optional. Phone number of the contact associated with the address. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/retail_location_resp.py b/src/ucp_sdk/models/schemas/shopping/types/retail_location.py similarity index 76% rename from src/ucp_sdk/models/schemas/shopping/types/retail_location_resp.py rename to src/ucp_sdk/models/schemas/shopping/types/retail_location.py index 951fe59..c0353af 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/retail_location_resp.py +++ b/src/ucp_sdk/models/schemas/shopping/types/retail_location.py @@ -19,24 +19,27 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict + from . import postal_address -class RetailLocationResponse(BaseModel): - """A pickup location (retail store, locker, etc.).""" +class RetailLocation(BaseModel): + """ + A pickup location (retail store, locker, etc.). + """ - model_config = ConfigDict( - extra="allow", - ) - id: str - """ + model_config = ConfigDict( + extra="allow", + ) + id: str + """ Unique location identifier. """ - name: str - """ + name: str + """ Location name (e.g., store name). """ - address: postal_address.PostalAddress | None = None - """ + address: postal_address.PostalAddress | None = None + """ Physical address of the location. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_req.py b/src/ucp_sdk/models/schemas/shopping/types/shipping_destination.py similarity index 83% rename from src/ucp_sdk/models/schemas/shopping/types/shipping_destination_req.py rename to src/ucp_sdk/models/schemas/shopping/types/shipping_destination.py index 1bba351..f8b929d 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/shipping_destination.py @@ -19,16 +19,19 @@ from __future__ import annotations from pydantic import ConfigDict + from .postal_address import PostalAddress -class ShippingDestinationRequest(PostalAddress): - """Shipping destination.""" +class ShippingDestination(PostalAddress): + """ + Shipping destination. + """ - model_config = ConfigDict( - extra="allow", - ) - id: str | None = None - """ + model_config = ConfigDict( + extra="allow", + ) + id: str + """ ID specific to this shipping destination. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_resp.py b/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_resp.py deleted file mode 100644 index 6ba62ae..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_resp.py +++ /dev/null @@ -1,34 +0,0 @@ -# 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 .postal_address import PostalAddress - - -class ShippingDestinationResponse(PostalAddress): - """Shipping destination.""" - - model_config = ConfigDict( - extra="allow", - ) - id: str - """ - ID specific to this shipping destination. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/token_credential_create_req.py b/src/ucp_sdk/models/schemas/shopping/types/token_credential.py similarity index 67% rename from src/ucp_sdk/models/schemas/shopping/types/token_credential_create_req.py rename to src/ucp_sdk/models/schemas/shopping/types/token_credential.py index ed52d78..596f097 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/token_credential_create_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/token_credential.py @@ -18,20 +18,24 @@ from __future__ import annotations -from pydantic import BaseModel, ConfigDict +from pydantic import ConfigDict +from .payment_credential import PaymentCredential -class TokenCredentialCreateRequest(BaseModel): - """Base token credential schema. Concrete payment handlers may extend this schema with additional fields and define their own constraints.""" - model_config = ConfigDict( - extra="allow", - ) - type: str - """ +class TokenCredential(PaymentCredential): + """ + Base token credential schema. Concrete payment handlers may extend this schema with additional fields and define their own constraints. + """ + + model_config = ConfigDict( + extra="allow", + ) + type: str + """ The specific type of token produced by the handler (e.g., 'stripe_token'). """ - token: str - """ + token: str + """ The token value. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/token_credential_resp.py b/src/ucp_sdk/models/schemas/shopping/types/token_credential_resp.py deleted file mode 100644 index 1cf6844..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/token_credential_resp.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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 TokenCredentialResponse(BaseModel): - """Base token credential schema. Concrete payment handlers may extend this schema with additional fields and define their own constraints.""" - - model_config = ConfigDict( - extra="allow", - ) - type: str - """ - The specific type of token produced by the handler (e.g., 'stripe_token'). - """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/token_credential_update_req.py b/src/ucp_sdk/models/schemas/shopping/types/token_credential_update_req.py deleted file mode 100644 index ff044f8..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/token_credential_update_req.py +++ /dev/null @@ -1,37 +0,0 @@ -# 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 TokenCredentialUpdateRequest(BaseModel): - """Base token credential schema. Concrete payment handlers may extend this schema with additional fields and define their own constraints.""" - - model_config = ConfigDict( - extra="allow", - ) - type: str - """ - The specific type of token produced by the handler (e.g., 'stripe_token'). - """ - token: str - """ - The token value. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/total_resp.py b/src/ucp_sdk/models/schemas/shopping/types/total.py similarity index 78% rename from src/ucp_sdk/models/schemas/shopping/types/total_resp.py rename to src/ucp_sdk/models/schemas/shopping/types/total.py index 8323cfb..320e79b 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/total_resp.py +++ b/src/ucp_sdk/models/schemas/shopping/types/total.py @@ -19,30 +19,23 @@ from __future__ import annotations from typing import Literal + from pydantic import BaseModel, ConfigDict, Field -class TotalResponse(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - type: Literal[ - "items_discount", - "subtotal", - "discount", - "fulfillment", - "tax", - "fee", - "total", - ] - """ +class Total(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + type: Literal["items_discount", "subtotal", "discount", "fulfillment", "tax", "fee", "total"] + """ Type of total categorization. """ - display_text: str | None = None - """ + display_text: str | None = None + """ Text to display against the amount. Should reflect appropriate method (e.g., 'Shipping', 'Delivery'). """ - amount: int = Field(..., ge=0) - """ + amount: int = Field(..., ge=0) + """ If type == total, sums subtotal - discount + fulfillment + tax + fee. Should be >= 0. Amount in minor (cents) currency units. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/total_create_req.py b/src/ucp_sdk/models/schemas/shopping/types/total_create_req.py deleted file mode 100644 index ab0c7d2..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/total_create_req.py +++ /dev/null @@ -1,27 +0,0 @@ -# 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 TotalCreateRequest(BaseModel): - model_config = ConfigDict( - extra="allow", - ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/total_update_req.py b/src/ucp_sdk/models/schemas/shopping/types/total_update_req.py deleted file mode 100644 index ef0dfe3..0000000 --- a/src/ucp_sdk/models/schemas/shopping/types/total_update_req.py +++ /dev/null @@ -1,27 +0,0 @@ -# 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 TotalUpdateRequest(BaseModel): - model_config = ConfigDict( - extra="allow", - ) diff --git a/src/ucp_sdk/models/schemas/transports/__init__.py b/src/ucp_sdk/models/schemas/transports/__init__.py new file mode 100644 index 0000000..421dc21 --- /dev/null +++ b/src/ucp_sdk/models/schemas/transports/__init__.py @@ -0,0 +1,18 @@ +# 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/shopping/types/fulfillment_group_update_req.py b/src/ucp_sdk/models/schemas/transports/embedded_config.py similarity index 60% rename from src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_update_req.py rename to src/ucp_sdk/models/schemas/transports/embedded_config.py index 6b6893b..8f3cba7 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_update_req.py +++ b/src/ucp_sdk/models/schemas/transports/embedded_config.py @@ -21,17 +21,15 @@ from pydantic import BaseModel, ConfigDict -class FulfillmentGroupUpdateRequest(BaseModel): - """A merchant-generated package/group of line items with fulfillment options.""" +class EmbeddedTransportConfig(BaseModel): + """ + Per-checkout configuration for embedded transport binding. Allows businesses to vary ECP availability and delegations based on cart contents, agent authorization, or policy. + """ - model_config = ConfigDict( - extra="allow", - ) - id: str - """ - Group identifier for referencing merchant-generated groups in updates. + model_config = ConfigDict( + extra="allow", + ) + delegate: list[str] | None = None """ - selected_option_id: str | None = None - """ - ID of the selected fulfillment option for this group. + Delegations the business allows. At service-level, declares available delegations. In checkout responses, confirms accepted delegations for this session. """ diff --git a/src/ucp_sdk/models/schemas/ucp.py b/src/ucp_sdk/models/schemas/ucp.py index d64119d..d1ce414 100644 --- a/src/ucp_sdk/models/schemas/ucp.py +++ b/src/ucp_sdk/models/schemas/ucp.py @@ -18,20 +18,160 @@ from __future__ import annotations -from .._internal import ( - DiscoveryProfile, - ResponseCheckout, - ResponseOrder, - Services, - UcpMetadata, - Version, -) - -__all__ = [ - "DiscoveryProfile", - "ResponseCheckout", - "ResponseOrder", - "Services", - "UcpMetadata", - "Version", -] +from typing import Any + +from pydantic import AnyUrl, BaseModel, ConfigDict, Field, RootModel + +from . import capability, payment_handler, service + + +class UcpMetadata(RootModel[Any]): + root: Any = Field(..., title="UCP Metadata") + """ + Protocol metadata for discovery profiles and responses. Uses slim schema pattern with context-specific required fields. + """ + + +class Version(RootModel[str]): + root: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$") + """ + UCP version in YYYY-MM-DD format. + """ + + +class ReverseDomainName(RootModel[str]): + root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") + """ + Reverse-domain identifier (e.g., com.google.pay, dev.ucp.shopping.checkout) + """ + + +class Entity(BaseModel): + """ + Shared foundation for all UCP entities. + """ + + model_config = ConfigDict( + extra="allow", + ) + version: Version + """ + Entity version in YYYY-MM-DD format. + """ + spec: AnyUrl | None = None + """ + URL to human-readable specification document. + """ + schema_: AnyUrl | None = Field(None, alias="schema") + """ + URL to JSON Schema defining this entity's structure and payloads. + """ + id: str | None = None + """ + Unique identifier for this entity instance. Used to disambiguate when multiple instances exist. + """ + config: dict[str, Any] | None = None + """ + Entity-specific configuration. Structure defined by each entity's schema. + """ + + +class PlatformSchema(BaseModel): + """ + Full UCP metadata for platform-level configuration. Hosted at a URI advertised by the platform in request headers. + """ + + model_config = ConfigDict( + extra="allow", + ) + version: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$") + """ + UCP version in YYYY-MM-DD format. + """ + services: Any + capabilities: Any | None = None + payment_handlers: Any + + +class BusinessSchema(BaseModel): + """ + UCP metadata for business/merchant-level configuration. Subset of platform schema with business-specific settings. + """ + + model_config = ConfigDict( + extra="allow", + ) + version: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$") + """ + UCP version in YYYY-MM-DD format. + """ + services: Any + capabilities: Any | None = None + payment_handlers: Any + + +class ResponseCheckoutSchema(BaseModel): + """ + UCP metadata for checkout responses. + """ + + model_config = ConfigDict( + extra="allow", + ) + version: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$") + """ + UCP version in YYYY-MM-DD format. + """ + services: Any | None = None + capabilities: Any | None = None + payment_handlers: Any + + +class ResponseCartSchema(RootModel[Any]): + root: Any + + +class Base(BaseModel): + """ + Base UCP metadata with shared properties for all schema types. + """ + + model_config = ConfigDict( + extra="allow", + ) + version: Version + services: dict[ReverseDomainName, list[service.Base]] | None = None + """ + Service registry keyed by reverse-domain name. + """ + capabilities: dict[ReverseDomainName, list[capability.Base]] | None = None + """ + Capability registry keyed by reverse-domain name. + """ + payment_handlers: dict[ReverseDomainName, list[payment_handler.Base]] | None = None + """ + Payment handler registry keyed by reverse-domain name. + """ + + +class ResponseOrderSchema(BaseModel): + """ + UCP metadata for order responses. No payment handlers needed post-purchase. + """ + + model_config = ConfigDict( + extra="allow", + ) + version: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$") + """ + UCP version in YYYY-MM-DD format. + """ + services: dict[str, list[service.Base]] | None = None + """ + Service registry keyed by reverse-domain name. + """ + capabilities: Any | None = None + payment_handlers: dict[str, list[payment_handler.Base]] | None = None + """ + Payment handler registry keyed by reverse-domain name. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/retail_location_req.py b/src/ucp_sdk/models/services/shopping/embedded.py similarity index 62% rename from src/ucp_sdk/models/schemas/shopping/types/retail_location_req.py rename to src/ucp_sdk/models/services/shopping/embedded.py index 6b1a8e2..7da4251 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/retail_location_req.py +++ b/src/ucp_sdk/models/services/shopping/embedded.py @@ -18,21 +18,13 @@ from __future__ import annotations -from pydantic import BaseModel, ConfigDict -from . import postal_address +from typing import Any +from pydantic import Field, RootModel -class RetailLocationRequest(BaseModel): - """A pickup location (retail store, locker, etc.).""" - model_config = ConfigDict( - extra="allow", - ) - name: str - """ - Location name (e.g., store name). +class EmbeddedProtocol(RootModel[Any]): + root: Any = Field(..., title="Embedded Protocol") """ - address: postal_address.PostalAddress | None = None - """ - Physical address of the location. + Embedded Protocol (EP) methods for UCP capabilities. Methods are sent from Merchant to Host via postMessage/JSON-RPC 2.0. Method prefixes indicate capability scope: ec.* (checkout). Future capabilities may define additional prefixes (e.g., eo.* for order). """ diff --git a/src/ucp_sdk/models/services/shopping/embedded_openrpc.py b/src/ucp_sdk/models/services/shopping/openapi.py similarity index 98% rename from src/ucp_sdk/models/services/shopping/embedded_openrpc.py rename to src/ucp_sdk/models/services/shopping/openapi.py index e8b3b8c..66b2502 100644 --- a/src/ucp_sdk/models/services/shopping/embedded_openrpc.py +++ b/src/ucp_sdk/models/services/shopping/openapi.py @@ -19,8 +19,9 @@ from __future__ import annotations from typing import Any + from pydantic import RootModel class Model(RootModel[Any]): - root: Any + root: Any diff --git a/src/ucp_sdk/models/services/shopping/mcp_openrpc.py b/src/ucp_sdk/models/services/shopping/openrpc.py similarity index 98% rename from src/ucp_sdk/models/services/shopping/mcp_openrpc.py rename to src/ucp_sdk/models/services/shopping/openrpc.py index e8b3b8c..66b2502 100644 --- a/src/ucp_sdk/models/services/shopping/mcp_openrpc.py +++ b/src/ucp_sdk/models/services/shopping/openrpc.py @@ -19,8 +19,9 @@ from __future__ import annotations from typing import Any + from pydantic import RootModel class Model(RootModel[Any]): - root: Any + root: Any diff --git a/src/ucp_sdk/py.typed b/src/ucp_sdk/py.typed deleted file mode 100644 index e69de29..0000000 From 6d46afbde4a4d77698fa382954059977efa09a5b Mon Sep 17 00:00:00 2001 From: damaz91 Date: Fri, 6 Feb 2026 10:13:10 +0000 Subject: [PATCH 2/7] add init at the base level --- src/ucp_sdk/__init__.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/ucp_sdk/__init__.py diff --git a/src/ucp_sdk/__init__.py b/src/ucp_sdk/__init__.py new file mode 100644 index 0000000..fd754fb --- /dev/null +++ b/src/ucp_sdk/__init__.py @@ -0,0 +1,19 @@ +# 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.""" + + +def hello() -> str: + return "Hello from ucp-python-sdk!" \ No newline at end of file From 921471297941b97987f2249ce0f47a37f4bfeec7 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Fri, 6 Feb 2026 10:17:24 +0000 Subject: [PATCH 3/7] update README --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 729cc17..d6da2e4 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,7 @@ For now, you can install the SDK using the following commands: mkdir sdk # Clone the repository -git clone https://github.com/Universal-Commerce-Protocol/python-sdk.git sdk/python - -# Navigate to the directory -cd sdk/python +git clone https://github.com/Universal-Commerce-Protocol/python-sdk.git # Install dependencies uv sync @@ -58,6 +55,12 @@ This project uses `uv` for dependency management. The models are automatically generated from the JSON schemas in the UCP Specification. +Clone the UCP main repository in the same folder: + +```bash +git clone https://github.com/Universal-Commerce-Protocol/ucp.git +``` + To regenerate the models: ```bash From 6b8407000260f4f60f782c1c2383598dbe141e30 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Fri, 6 Feb 2026 11:14:14 +0000 Subject: [PATCH 4/7] add request specific classes --- generate_models.sh | 2 +- preprocess_schemas.py | 207 ++++++++++++- .../models/discovery/profile_schema.py | 2 +- src/ucp_sdk/models/schemas/service.py | 4 - src/ucp_sdk/models/schemas/shopping/cart.py | 28 +- .../schemas/shopping/cart_create_request.py | 47 +++ .../schemas/shopping/cart_update_request.py | 51 ++++ .../shopping/checkout_complete_request.py | 34 +++ .../shopping/checkout_create_request.py | 71 +++++ .../shopping/checkout_update_request.py | 75 +++++ .../types/fulfillment_group_create_request.py | 35 +++ .../types/fulfillment_group_update_request.py | 39 +++ .../fulfillment_method_create_request.py | 55 ++++ .../fulfillment_method_update_request.py | 53 ++++ .../types/line_item_create_request.py | 38 +++ .../types/line_item_update_request.py | 43 +++ src/ucp_sdk/models/schemas/ucp.py | 276 ++++++++++++++++-- ucp | 1 + 18 files changed, 1029 insertions(+), 32 deletions(-) create mode 100644 src/ucp_sdk/models/schemas/shopping/cart_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/cart_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/checkout_complete_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/checkout_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/checkout_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/line_item_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/line_item_update_request.py create mode 160000 ucp diff --git a/generate_models.sh b/generate_models.sh index 7826da2..297a26e 100755 --- a/generate_models.sh +++ b/generate_models.sh @@ -72,7 +72,7 @@ echo "Formatting generated models..." uv run ruff format "$OUTPUT_DIR" uv run ruff check --fix --config "$OUTPUT_DIR/ruff.toml" "$OUTPUT_DIR" 2>&1 | grep -E "^(All checks passed|Fixed|Found)" || echo "Formatting complete" -# Clean up temp schemas +# Clean up temporary schemas rm -rf "$TEMP_SCHEMA_DIR" echo "Done. Models generated in $OUTPUT_DIR" diff --git a/preprocess_schemas.py b/preprocess_schemas.py index a83004f..1dea8ac 100644 --- a/preprocess_schemas.py +++ b/preprocess_schemas.py @@ -142,23 +142,202 @@ def flatten_allof_in_defs(schema: Dict[str, Any]) -> Dict[str, Any]: return schema -def preprocess_schema_file(input_path: Path, output_path: Path) -> None: +def update_refs_for_scenario(obj: Any, scenario: str, schemas_with_scenarios: set, current_dir: str = "") -> Any: + """ + Recursively update $ref paths to point to scenario-specific schemas. + For example, changes "types/line_item.json" to "types/line_item_create_request.json" for create scenario. + Only updates references to schemas that actually have scenarios. + """ + if isinstance(obj, dict): + result = {} + for key, value in obj.items(): + if key == "$ref" and isinstance(value, str): + # Skip internal references and references with fragments (e.g., "../ucp.json#/$defs/...") + if value.startswith("#/") or "#/$defs/" in value: + result[key] = value + # Check if this is a reference to a schema file (not internal #/$defs/) + elif value.endswith(".json"): + # Resolve the relative path from current schema's directory + if current_dir: + ref_path = str(Path(current_dir) / value) + else: + ref_path = value + + # Normalize the path + ref_path = ref_path.replace("\\", "/") + + # Check if the referenced schema has scenarios + if ref_path in schemas_with_scenarios: + base_path = value[:-5] # Remove .json + # Create scenario-specific reference + result[key] = f"{base_path}_{scenario}_request.json" + else: + # Keep original reference for schemas without scenarios + result[key] = value + else: + result[key] = value + else: + result[key] = update_refs_for_scenario(value, scenario, schemas_with_scenarios, current_dir) + return result + elif isinstance(obj, list): + return [update_refs_for_scenario(item, scenario, schemas_with_scenarios, current_dir) for item in obj] + return obj + + +def process_ucp_request_scenarios(schema: Dict[str, Any], base_name: str, schemas_with_scenarios: set, schema_dir: str = "") -> Dict[str, Dict[str, Any]]: + """ + Generate separate schemas for create, update, and complete scenarios based on ucp_request annotations. + + Returns a dict mapping scenario names (e.g., 'create', 'update', 'complete') to their schemas. + """ + scenarios = {} + + # Check if this schema has properties with ucp_request annotations + if "properties" not in schema: + return {"base": schema} + + # Detect which scenarios exist + scenario_types = set() + for prop_name, prop_schema in schema["properties"].items(): + if isinstance(prop_schema, dict) and "ucp_request" in prop_schema: + ucp_req = prop_schema["ucp_request"] + if isinstance(ucp_req, dict): + scenario_types.update(ucp_req.keys()) + + # If no scenarios detected, return base schema + if not scenario_types: + return {"base": schema} + + # Generate a schema for each scenario + for scenario in scenario_types: + scenario_schema = copy.deepcopy(schema) + + # Update title and description to reflect the scenario + if "title" in scenario_schema: + scenario_schema["title"] = f"{scenario_schema['title']} ({scenario.capitalize()} Request)" + + # Process each property based on its ucp_request directive + properties_to_remove = [] + required_fields = set(scenario_schema.get("required", [])) + + for prop_name, prop_schema in scenario_schema["properties"].items(): + if isinstance(prop_schema, dict) and "ucp_request" in prop_schema: + ucp_req = prop_schema["ucp_request"] + + # Clean up the ucp_request annotation from the property + del prop_schema["ucp_request"] + + # Determine the directive for this scenario + if isinstance(ucp_req, str): + directive = ucp_req + elif isinstance(ucp_req, dict): + directive = ucp_req.get(scenario, "optional") + else: + directive = "optional" + + # Handle the directive + if directive == "omit": + properties_to_remove.append(prop_name) + required_fields.discard(prop_name) + elif directive == "required": + required_fields.add(prop_name) + elif directive == "optional": + required_fields.discard(prop_name) + + # Remove omitted properties + for prop_name in properties_to_remove: + del scenario_schema["properties"][prop_name] + + # Update required fields + if required_fields: + scenario_schema["required"] = sorted(list(required_fields)) + elif "required" in scenario_schema: + del scenario_schema["required"] + + # Update all $ref paths to point to scenario-specific schemas + scenario_schema = update_refs_for_scenario(scenario_schema, scenario, schemas_with_scenarios, schema_dir) + + scenarios[scenario] = scenario_schema + + return scenarios + + +def clean_schema_for_codegen(schema: Dict[str, Any]) -> Dict[str, Any]: + """ + Clean up schema to avoid issues with datamodel-code-generator. + - Remove $id fields that might cause URL resolution issues + - Remove $schema fields + """ + schema = copy.deepcopy(schema) + + # Remove fields that can cause URL resolution issues + if "$id" in schema: + del schema["$id"] + if "$schema" in schema: + del schema["$schema"] + + return schema + + +def preprocess_schema_file(input_path: Path, output_path: Path, schemas_with_scenarios: set) -> None: """Preprocess a single schema file.""" with open(input_path, 'r', encoding='utf-8') as f: schema = json.load(f) + # WARN: Workaround for datamodel-codegen issue with relative paths + # When ucp.json is referenced from a subdirectory (e.g. schemas/shopping/checkout.json referencing ../ucp.json), + # the subsequent reference to service.json (in ucp.json) is incorrectly resolved relative to the subdirectory. + # We force an absolute path here. + if input_path.name == "ucp.json": + # Force absolute paths for all sibling references in ucp.json + # This fixes resolution issues when ucp.json is referenced from subdirectories + schema_str = json.dumps(schema) + + input_dir_abs = output_path.parent.resolve() + for ref_file in ["service.json", "capability.json", "payment_handler.json"]: + ref_path = input_dir_abs / ref_file + schema_str = schema_str.replace(f'"{ref_file}', f'"file://{ref_path}') + + schema = json.loads(schema_str) + # Remove extension definitions that reference external schemas schema = remove_extension_defs(schema) # Flatten allOf patterns within $defs that only use internal refs schema = flatten_allof_in_defs(schema) + # Generate scenario-specific schemas + base_name = output_path.stem # filename without extension + + # Get the directory of this schema relative to the root (for resolving refs) + schema_dir = str(output_path.parent.relative_to(output_path.parent.parent.parent)) + + scenarios = process_ucp_request_scenarios(schema, base_name, schemas_with_scenarios, schema_dir) + # Ensure output directory exists output_path.parent.mkdir(parents=True, exist_ok=True) - # Write preprocessed schema - with open(output_path, 'w', encoding='utf-8') as f: - json.dump(schema, f, indent=2) + # Write preprocessed schema(s) + if len(scenarios) == 1 and "base" in scenarios: + # No scenarios, write single file + cleaned = clean_schema_for_codegen(scenarios["base"]) + with open(output_path, 'w', encoding='utf-8') as f: + json.dump(cleaned, f, indent=2) + else: + # Write both base schema and scenario-specific files + # First, write the base schema (for non-scenario-specific references) + base_schema = copy.deepcopy(schema) + cleaned_base = clean_schema_for_codegen(base_schema) + with open(output_path, 'w', encoding='utf-8') as f: + json.dump(cleaned_base, f, indent=2) + + # Then write separate files for each scenario + for scenario_name, scenario_schema in scenarios.items(): + scenario_path = output_path.parent / f"{base_name}_{scenario_name}_request.json" + print(f" -> Generating {scenario_name} request schema") + cleaned = clean_schema_for_codegen(scenario_schema) + with open(scenario_path, 'w', encoding='utf-8') as f: + json.dump(cleaned, f, indent=2) def preprocess_schemas(input_dir: Path, output_dir: Path) -> None: @@ -173,13 +352,31 @@ def preprocess_schemas(input_dir: Path, output_dir: Path) -> None: print(f"Preprocessing {len(json_files)} schema files...") + # First pass: track which schemas have scenarios + schemas_with_scenarios = set() + for json_file in json_files: + with open(json_file, 'r', encoding='utf-8') as f: + schema = json.load(f) + + # Check if this schema has ucp_request scenarios + if "properties" in schema: + for prop_schema in schema["properties"].values(): + if isinstance(prop_schema, dict) and "ucp_request" in prop_schema: + ucp_req = prop_schema["ucp_request"] + if isinstance(ucp_req, dict): + # This schema has scenarios + rel_path = json_file.relative_to(input_dir) + schemas_with_scenarios.add(str(rel_path)) + break + + # Second pass: preprocess and generate schemas for json_file in json_files: # Calculate relative path rel_path = json_file.relative_to(input_dir) output_path = output_dir / rel_path print(f" Processing: {rel_path}") - preprocess_schema_file(json_file, output_path) + preprocess_schema_file(json_file, output_path, schemas_with_scenarios) print(f"Preprocessing complete. Output in {output_dir}") diff --git a/src/ucp_sdk/models/discovery/profile_schema.py b/src/ucp_sdk/models/discovery/profile_schema.py index 68ec227..42e446c 100644 --- a/src/ucp_sdk/models/discovery/profile_schema.py +++ b/src/ucp_sdk/models/discovery/profile_schema.py @@ -116,7 +116,7 @@ class Base(BaseModel): model_config = ConfigDict( extra="allow", ) - ucp: ucp_1.Base + ucp: ucp_1.BaseModel1 signing_keys: list[SigningKey] | None = None """ Public keys for signature verification (JWK format). Used to verify signed responses, webhooks, and other authenticated messages from this party. diff --git a/src/ucp_sdk/models/schemas/service.py b/src/ucp_sdk/models/schemas/service.py index 227ca16..5c619d2 100644 --- a/src/ucp_sdk/models/schemas/service.py +++ b/src/ucp_sdk/models/schemas/service.py @@ -191,7 +191,3 @@ class ResponseSchema2(RootModel[ResponseSchema | ResponseSchema4 | ResponseSchem """ Service binding in API responses. Includes per-resource transport configuration via typed config. """ - - -class Base(RootModel[Any]): - root: Any diff --git a/src/ucp_sdk/models/schemas/shopping/cart.py b/src/ucp_sdk/models/schemas/shopping/cart.py index d66c2c7..7110dfc 100644 --- a/src/ucp_sdk/models/schemas/shopping/cart.py +++ b/src/ucp_sdk/models/schemas/shopping/cart.py @@ -20,8 +20,9 @@ from typing import Any -from pydantic import AnyUrl, AwareDatetime, BaseModel, ConfigDict +from pydantic import AnyUrl, AwareDatetime, BaseModel, ConfigDict, Field +from .. import ucp as ucp_1 from .types import line_item, link, message, total @@ -77,6 +78,29 @@ class Buyer(BaseModel): """ +class Ucp(BaseModel): + """ + UCP metadata for cart responses. No payment handlers needed pre-checkout. + """ + + model_config = ConfigDict( + extra="allow", + ) + version: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$") + """ + UCP version in YYYY-MM-DD format. + """ + services: dict[str, list[ucp_1.Base]] | None = None + """ + Service registry keyed by reverse-domain name. + """ + capabilities: Any | None = None + payment_handlers: dict[str, list[ucp_1.Base]] | None = None + """ + Payment handler registry keyed by reverse-domain name. + """ + + class Cart(BaseModel): """ Shopping cart with estimated pricing before checkout. Lightweight pre-purchase exploration with no payment info or complex status states. Cart exists (200) or doesn't (404). @@ -85,7 +109,7 @@ class Cart(BaseModel): model_config = ConfigDict( extra="allow", ) - ucp: Any + ucp: Ucp id: str """ Unique cart identifier. diff --git a/src/ucp_sdk/models/schemas/shopping/cart_create_request.py b/src/ucp_sdk/models/schemas/shopping/cart_create_request.py new file mode 100644 index 0000000..f948b9a --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/cart_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 + +from .types import buyer as buyer_1 +from .types import context as context_1 +from .types import line_item_create_request + + +class CartCreateRequest(BaseModel): + """ + Shopping cart with estimated pricing before checkout. Lightweight pre-purchase exploration with no payment info or complex status states. Cart exists (200) or doesn't (404). + """ + + model_config = ConfigDict( + extra="allow", + ) + line_items: list[line_item_create_request.LineItemCreateRequest] + """ + Cart line items. Same structure as checkout. Full replacement on update. + """ + context: context_1.Context | None = None + """ + Buyer signals for localization (country, region, postal_code). Merchant uses for pricing, availability, currency. Falls back to geo-IP if omitted. + """ + buyer: buyer_1.Buyer | None = None + """ + Optional buyer information for personalized estimates. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/cart_update_request.py b/src/ucp_sdk/models/schemas/shopping/cart_update_request.py new file mode 100644 index 0000000..3815fdf --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/cart_update_request.py @@ -0,0 +1,51 @@ +# 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 .types import buyer as buyer_1 +from .types import context as context_1 +from .types import line_item_update_request + + +class CartUpdateRequest(BaseModel): + """ + Shopping cart with estimated pricing before checkout. Lightweight pre-purchase exploration with no payment info or complex status states. Cart exists (200) or doesn't (404). + """ + + 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. + """ + context: context_1.Context | None = None + """ + Buyer signals for localization (country, region, postal_code). Merchant uses for pricing, availability, currency. Falls back to geo-IP if omitted. + """ + buyer: buyer_1.Buyer | None = None + """ + Optional buyer information for personalized estimates. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/checkout_complete_request.py b/src/ucp_sdk/models/schemas/shopping/checkout_complete_request.py new file mode 100644 index 0000000..5799fc2 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/checkout_complete_request.py @@ -0,0 +1,34 @@ +# 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 payment as payment_1 + + +class CheckoutCompleteRequest(BaseModel): + """ + Base checkout schema. Extensions compose onto this using allOf. + """ + + model_config = ConfigDict( + extra="allow", + ) + payment: payment_1.Payment diff --git a/src/ucp_sdk/models/schemas/shopping/checkout_create_request.py b/src/ucp_sdk/models/schemas/shopping/checkout_create_request.py new file mode 100644 index 0000000..fccc424 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/checkout_create_request.py @@ -0,0 +1,71 @@ +# 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 payment as payment_1 +from .types import buyer as buyer_1 +from .types import line_item_create_request + + +class Context(BaseModel): + """ + Provisional buyer signals for relevance and localization: product availability, pricing, currency, tax, shipping, payment methods, and eligibility (e.g., student or affiliation discounts). Businesses SHOULD use these values when authoritative data (e.g., address) is absent, and MAY ignore unsupported values without returning errors. 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. Platforms SHOULD progressively enhance context throughout the buyer journey. + """ + + model_config = ConfigDict( + extra="allow", + ) + address_country: str | None = None + """ + 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. + """ + address_region: 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. + """ + postal_code: str | None = None + """ + The postal code. For example, 94043. Optional hint for regional refinement—higher-resolution data (e.g., shipping address) supersedes this value. + """ + intent: str | None = None + """ + Background context describing buyer's intent (e.g., 'looking for a gift under $50', 'need something durable for outdoor use'). Informs relevance, recommendations, and personalization. + """ + + +class CheckoutCreateRequest(BaseModel): + """ + Base checkout schema. Extensions compose onto this using allOf. + """ + + model_config = ConfigDict( + extra="allow", + ) + line_items: list[line_item_create_request.LineItemCreateRequest] + """ + List of line items being checked out. + """ + buyer: buyer_1.Buyer | None = None + """ + Representation of the buyer. + """ + context: Context | None = None + payment: payment_1.Payment | None = None diff --git a/src/ucp_sdk/models/schemas/shopping/checkout_update_request.py b/src/ucp_sdk/models/schemas/shopping/checkout_update_request.py new file mode 100644 index 0000000..8e2b1c9 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/checkout_update_request.py @@ -0,0 +1,75 @@ +# 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 payment as payment_1 +from .types import buyer as buyer_1 +from .types import line_item_update_request + + +class Context(BaseModel): + """ + Provisional buyer signals for relevance and localization: product availability, pricing, currency, tax, shipping, payment methods, and eligibility (e.g., student or affiliation discounts). Businesses SHOULD use these values when authoritative data (e.g., address) is absent, and MAY ignore unsupported values without returning errors. 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. Platforms SHOULD progressively enhance context throughout the buyer journey. + """ + + model_config = ConfigDict( + extra="allow", + ) + address_country: str | None = None + """ + 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. + """ + address_region: 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. + """ + postal_code: str | None = None + """ + The postal code. For example, 94043. Optional hint for regional refinement—higher-resolution data (e.g., shipping address) supersedes this value. + """ + intent: str | None = None + """ + Background context describing buyer's intent (e.g., 'looking for a gift under $50', 'need something durable for outdoor use'). Informs relevance, recommendations, and personalization. + """ + + +class CheckoutUpdateRequest(BaseModel): + """ + Base checkout schema. Extensions compose onto this using allOf. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + Unique identifier of the checkout session. + """ + line_items: list[line_item_update_request.LineItemUpdateRequest] + """ + List of line items being checked out. + """ + buyer: buyer_1.Buyer | None = None + """ + Representation of the buyer. + """ + context: Context | None = None + payment: payment_1.Payment | None = None diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_create_request.py new file mode 100644 index 0000000..99489e4 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_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 BaseModel, ConfigDict + + +class FulfillmentGroupCreateRequest(BaseModel): + """ + A merchant-generated package/group of line items with fulfillment options. + """ + + model_config = ConfigDict( + extra="allow", + ) + selected_option_id: str | None = None + """ + ID of the selected fulfillment option for this group. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_update_request.py new file mode 100644 index 0000000..c830c71 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_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 + + +class FulfillmentGroupUpdateRequest(BaseModel): + """ + A merchant-generated package/group of line items with fulfillment options. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + Group identifier for referencing merchant-generated groups in updates. + """ + selected_option_id: str | None = None + """ + ID of the selected fulfillment option for this group. + """ 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 new file mode 100644 index 0000000..8ee2ebc --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_create_request.py @@ -0,0 +1,55 @@ +# 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 fulfillment_destination, fulfillment_group + + +class FulfillmentMethodCreateRequest(BaseModel): + """ + A fulfillment method (shipping or pickup) 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.FulfillmentDestination] | None = None + """ + Available destinations. For shipping: addresses. For pickup: retail locations. + """ + selected_destination_id: str | None = None + """ + ID of the selected destination. + """ + groups: list[fulfillment_group.FulfillmentGroup] | None = None + """ + Fulfillment groups for selecting options. Agent sets selected_option_id on groups to choose shipping method. + """ 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 new file mode 100644 index 0000000..e5796c2 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_request.py @@ -0,0 +1,53 @@ +# 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 fulfillment_destination, fulfillment_group + + +class FulfillmentMethodUpdateRequest(BaseModel): + """ + A fulfillment method (shipping or pickup) with destinations and groups. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + Unique fulfillment method identifier. + """ + line_item_ids: list[str] + """ + Line item IDs fulfilled via this method. + """ + destinations: list[fulfillment_destination.FulfillmentDestination] | None = None + """ + Available destinations. For shipping: addresses. For pickup: retail locations. + """ + selected_destination_id: str | None = None + """ + ID of the selected destination. + """ + groups: list[fulfillment_group.FulfillmentGroup] | None = None + """ + Fulfillment groups for selecting options. Agent sets selected_option_id on groups to choose shipping method. + """ 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 new file mode 100644 index 0000000..e760d41 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/line_item_create_request.py @@ -0,0 +1,38 @@ +# 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 item as item_1 + + +class LineItemCreateRequest(BaseModel): + """ + Line item object. Expected to use the currency of the parent object. + """ + + model_config = ConfigDict( + extra="allow", + ) + item: item_1.Item + quantity: int = Field(..., ge=1) + """ + Quantity of the item being purchased. + """ 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 new file mode 100644 index 0000000..c85a360 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/line_item_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 + +from . import item as item_1 + + +class LineItemUpdateRequest(BaseModel): + """ + Line item object. Expected to use the currency of the parent object. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str | None = None + item: item_1.Item + quantity: int = Field(..., ge=1) + """ + Quantity of the item being purchased. + """ + parent_id: str | None = None + """ + Parent line item identifier for any nested structures. + """ diff --git a/src/ucp_sdk/models/schemas/ucp.py b/src/ucp_sdk/models/schemas/ucp.py index d1ce414..ca18a9a 100644 --- a/src/ucp_sdk/models/schemas/ucp.py +++ b/src/ucp_sdk/models/schemas/ucp.py @@ -18,12 +18,10 @@ from __future__ import annotations -from typing import Any +from typing import Any, Literal from pydantic import AnyUrl, BaseModel, ConfigDict, Field, RootModel -from . import capability, payment_handler, service - class UcpMetadata(RootModel[Any]): root: Any = Field(..., title="UCP Metadata") @@ -76,6 +74,206 @@ class Entity(BaseModel): """ +class Base(RootModel[Any]): + root: Any + + +class PlatformSchema9(BaseModel): + """ + Full service declaration for platform-level discovery. Different transports require different fields. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["rest"] = "rest" + + +class PlatformSchema10(BaseModel): + """ + Full service declaration for platform-level discovery. Different transports require different fields. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["mcp"] = "mcp" + + +class PlatformSchema11(BaseModel): + """ + Full service declaration for platform-level discovery. Different transports require different fields. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["a2a"] = "a2a" + + +class PlatformSchema12(BaseModel): + """ + Full service declaration for platform-level discovery. Different transports require different fields. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["embedded"] = "embedded" + + +class PlatformSchema8(RootModel[PlatformSchema9 | PlatformSchema10 | PlatformSchema11 | PlatformSchema12]): + root: PlatformSchema9 | PlatformSchema10 | PlatformSchema11 | PlatformSchema12 = Field( + ..., title="Service (Platform Schema)" + ) + """ + Full service declaration for platform-level discovery. Different transports require different fields. + """ + + +class BusinessSchema8(BaseModel): + """ + Service binding for business/merchant configuration. May override platform endpoints. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["rest"] = "rest" + + +class BusinessSchema9(BaseModel): + """ + Service binding for business/merchant configuration. May override platform endpoints. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["mcp"] = "mcp" + + +class BusinessSchema10(BaseModel): + """ + Service binding for business/merchant configuration. May override platform endpoints. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["a2a"] = "a2a" + + +class EmbeddedConfig(BaseModel): + """ + Per-checkout configuration for embedded transport binding. Allows businesses to vary ECP availability and delegations based on cart contents, agent authorization, or policy. + """ + + model_config = ConfigDict( + extra="allow", + ) + delegate: list[str] | None = None + """ + Delegations the business allows. At service-level, declares available delegations. In checkout responses, confirms accepted delegations for this session. + """ + + +class ResponseSchema(BaseModel): + """ + Service binding in API responses. Includes per-resource transport configuration via typed config. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["rest"] = "rest" + + +class ResponseSchema9(BaseModel): + """ + Service binding in API responses. Includes per-resource transport configuration via typed config. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["mcp"] = "mcp" + + +class ResponseSchema10(BaseModel): + """ + Service binding in API responses. Includes per-resource transport configuration via typed config. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["a2a"] = "a2a" + + +class ResponseSchema11(BaseModel): + """ + Service binding in API responses. Includes per-resource transport configuration via typed config. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["embedded"] = "embedded" + config: EmbeddedConfig | None = None + + +class ResponseSchema7(RootModel[ResponseSchema | ResponseSchema9 | ResponseSchema10 | ResponseSchema11]): + root: ResponseSchema | ResponseSchema9 | ResponseSchema10 | ResponseSchema11 = Field( + ..., title="Service (Response Schema)" + ) + """ + Service binding in API responses. Includes per-resource transport configuration via typed config. + """ + + +class PlatformSchema13(RootModel[Any]): + root: Any = Field(..., title="Capability (Platform Schema)") + """ + Full capability declaration for platform-level discovery. Includes spec/schema URLs for agent fetching. + """ + + +class BusinessSchema12(RootModel[Base]): + root: Base = Field(..., title="Capability (Business Schema)") + """ + Capability configuration for business/merchant level. May include business-specific config overrides. + """ + + +class ResponseSchema12(RootModel[Base]): + root: Base = Field(..., title="Capability (Response Schema)") + """ + Capability reference in responses. Only name/version required to confirm active capabilities. + """ + + +class PlatformSchema14(RootModel[Any]): + root: Any = Field(..., title="Payment Handler (Platform Schema)") + """ + Platform declaration for discovery profiles. May include partial config state required for discovery. + """ + + +class BusinessSchema13(RootModel[Base]): + root: Base = Field(..., title="Payment Handler (Business Schema)") + """ + Business declaration for discovery profiles. May include partial config state required for discovery. + """ + + +class ResponseSchema13(RootModel[Base]): + root: Base = Field(..., title="Payment Handler (Response Schema)") + """ + Handler reference in responses. May include full config state for runtime usage of the handler. + """ + + class PlatformSchema(BaseModel): """ Full UCP metadata for platform-level configuration. Hosted at a URI advertised by the platform in request headers. @@ -127,36 +325,32 @@ class ResponseCheckoutSchema(BaseModel): payment_handlers: Any -class ResponseCartSchema(RootModel[Any]): - root: Any - - -class Base(BaseModel): +class ResponseOrderSchema(BaseModel): """ - Base UCP metadata with shared properties for all schema types. + UCP metadata for order responses. No payment handlers needed post-purchase. """ model_config = ConfigDict( extra="allow", ) - version: Version - services: dict[ReverseDomainName, list[service.Base]] | None = None + version: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$") """ - Service registry keyed by reverse-domain name. + UCP version in YYYY-MM-DD format. """ - capabilities: dict[ReverseDomainName, list[capability.Base]] | None = None + services: dict[str, list[Base]] | None = None """ - Capability registry keyed by reverse-domain name. + Service registry keyed by reverse-domain name. """ - payment_handlers: dict[ReverseDomainName, list[payment_handler.Base]] | None = None + capabilities: Any | None = None + payment_handlers: dict[str, list[Base]] | None = None """ Payment handler registry keyed by reverse-domain name. """ -class ResponseOrderSchema(BaseModel): +class ResponseCartSchema(BaseModel): """ - UCP metadata for order responses. No payment handlers needed post-purchase. + UCP metadata for cart responses. No payment handlers needed pre-checkout. """ model_config = ConfigDict( @@ -166,12 +360,56 @@ class ResponseOrderSchema(BaseModel): """ UCP version in YYYY-MM-DD format. """ - services: dict[str, list[service.Base]] | None = None + services: dict[str, list[Base]] | None = None """ Service registry keyed by reverse-domain name. """ capabilities: Any | None = None - payment_handlers: dict[str, list[payment_handler.Base]] | None = None + payment_handlers: dict[str, list[Base]] | None = None """ Payment handler registry keyed by reverse-domain name. """ + + +class BaseModel1(BaseModel): + """ + Base UCP metadata with shared properties for all schema types. + """ + + model_config = ConfigDict( + extra="allow", + ) + version: Version + services: dict[ReverseDomainName, list[Base]] | None = None + """ + Service registry keyed by reverse-domain name. + """ + capabilities: dict[ReverseDomainName, list[Base]] | None = None + """ + Capability registry keyed by reverse-domain name. + """ + payment_handlers: dict[ReverseDomainName, list[Base]] | None = None + """ + Payment handler registry keyed by reverse-domain name. + """ + + +class BusinessSchema11(BaseModel): + """ + Service binding for business/merchant configuration. May override platform endpoints. + """ + + model_config = ConfigDict( + extra="allow", + ) + transport: Literal["embedded"] = "embedded" + config: EmbeddedConfig | None = None + + +class BusinessSchema7(RootModel[BusinessSchema8 | BusinessSchema9 | BusinessSchema10 | BusinessSchema11]): + root: BusinessSchema8 | BusinessSchema9 | BusinessSchema10 | BusinessSchema11 = Field( + ..., title="Service (Business Schema)" + ) + """ + Service binding for business/merchant configuration. May override platform endpoints. + """ diff --git a/ucp b/ucp new file mode 160000 index 0000000..cc50009 --- /dev/null +++ b/ucp @@ -0,0 +1 @@ +Subproject commit cc500091eb173ef6f1578cf8904b05015f6a7e06 From d339a83544fde227e4d1a4267ae5c2ef90f8516a Mon Sep 17 00:00:00 2001 From: damaz91 Date: Fri, 6 Feb 2026 11:38:46 +0000 Subject: [PATCH 5/7] fix schema generation --- preprocess_schemas.py | 20 ++++++--- ...illment_available_method_create_request.py | 31 ++++++++++++++ ...illment_available_method_update_request.py | 31 ++++++++++++++ .../types/fulfillment_create_request.py | 37 +++++++++++++++++ .../fulfillment_option_create_request.py | 31 ++++++++++++++ .../fulfillment_option_update_request.py | 31 ++++++++++++++ .../types/fulfillment_update_request.py | 37 +++++++++++++++++ .../shopping/types/item_create_request.py | 31 ++++++++++++++ .../shopping/types/item_update_request.py | 31 ++++++++++++++ .../types/retail_location_create_request.py | 41 +++++++++++++++++++ .../types/retail_location_update_request.py | 41 +++++++++++++++++++ .../shopping/types/total_create_request.py | 27 ++++++++++++ .../shopping/types/total_update_request.py | 27 ++++++++++++ ucp | 1 - 14 files changed, 410 insertions(+), 7 deletions(-) create mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/fulfillment_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/item_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/item_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/retail_location_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/retail_location_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/total_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/total_update_request.py delete mode 160000 ucp diff --git a/preprocess_schemas.py b/preprocess_schemas.py index 1dea8ac..fc15eb1 100644 --- a/preprocess_schemas.py +++ b/preprocess_schemas.py @@ -198,11 +198,20 @@ def process_ucp_request_scenarios(schema: Dict[str, Any], base_name: str, schema # Detect which scenarios exist scenario_types = set() + has_string_directive = False + for prop_name, prop_schema in schema["properties"].items(): if isinstance(prop_schema, dict) and "ucp_request" in prop_schema: ucp_req = prop_schema["ucp_request"] if isinstance(ucp_req, dict): scenario_types.update(ucp_req.keys()) + else: + has_string_directive = True + + # If no scenarios detected but plain strings exist, imply "create" and "update" scenarios + if not scenario_types and has_string_directive: + scenario_types.add("create") + scenario_types.add("update") # If no scenarios detected, return base schema if not scenario_types: @@ -362,12 +371,11 @@ def preprocess_schemas(input_dir: Path, output_dir: Path) -> None: if "properties" in schema: for prop_schema in schema["properties"].values(): if isinstance(prop_schema, dict) and "ucp_request" in prop_schema: - ucp_req = prop_schema["ucp_request"] - if isinstance(ucp_req, dict): - # This schema has scenarios - rel_path = json_file.relative_to(input_dir) - schemas_with_scenarios.add(str(rel_path)) - break + # If any ucp_request is present (dict or string), it has scenarios + rel_path = json_file.relative_to(input_dir) + path_str = str(rel_path).replace("\\", "/") # Normalize path separators + schemas_with_scenarios.add(path_str) + break # Second pass: preprocess and generate schemas for json_file in json_files: diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_create_request.py new file mode 100644 index 0000000..54854b7 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_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 FulfillmentAvailableMethodCreateRequest(BaseModel): + """ + Inventory availability hint for a fulfillment method type. + """ + + model_config = ConfigDict( + extra="allow", + ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_update_request.py new file mode 100644 index 0000000..678c052 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_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 FulfillmentAvailableMethodUpdateRequest(BaseModel): + """ + Inventory availability hint for a fulfillment method type. + """ + + model_config = ConfigDict( + extra="allow", + ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_create_request.py new file mode 100644 index 0000000..6b3a605 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_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 BaseModel, ConfigDict + +from . import fulfillment_method + + +class FulfillmentCreateRequest(BaseModel): + """ + Container for fulfillment methods and availability. + """ + + model_config = ConfigDict( + extra="allow", + ) + methods: list[fulfillment_method.FulfillmentMethod] | None = None + """ + Fulfillment methods for cart items. + """ 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 new file mode 100644 index 0000000..d08ee75 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_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 FulfillmentOptionCreateRequest(BaseModel): + """ + A fulfillment option within a group (e.g., Standard Shipping $5, Express $15). + """ + + model_config = ConfigDict( + extra="allow", + ) 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 new file mode 100644 index 0000000..363e7f3 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_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 FulfillmentOptionUpdateRequest(BaseModel): + """ + A fulfillment option within a group (e.g., Standard Shipping $5, Express $15). + """ + + model_config = ConfigDict( + extra="allow", + ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_update_request.py new file mode 100644 index 0000000..3422acb --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_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 BaseModel, ConfigDict + +from . import fulfillment_method + + +class FulfillmentUpdateRequest(BaseModel): + """ + Container for fulfillment methods and availability. + """ + + model_config = ConfigDict( + extra="allow", + ) + methods: list[fulfillment_method.FulfillmentMethod] | None = None + """ + Fulfillment methods for cart items. + """ 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 new file mode 100644 index 0000000..bbef8e8 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/item_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 ItemCreateRequest(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + 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. + """ 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 new file mode 100644 index 0000000..c957d16 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/item_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 ItemUpdateRequest(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + 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. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/retail_location_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/retail_location_create_request.py new file mode 100644 index 0000000..9de6eb1 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/retail_location_create_request.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 + +from . import postal_address + + +class RetailLocationCreateRequest(BaseModel): + """ + A pickup location (retail store, locker, etc.). + """ + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + Location name (e.g., store name). + """ + address: postal_address.PostalAddress | None = None + """ + Physical address of the location. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/retail_location_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/retail_location_update_request.py new file mode 100644 index 0000000..8e83ff4 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/retail_location_update_request.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 + +from . import postal_address + + +class RetailLocationUpdateRequest(BaseModel): + """ + A pickup location (retail store, locker, etc.). + """ + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + Location name (e.g., store name). + """ + address: postal_address.PostalAddress | None = None + """ + Physical address of the location. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/total_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/total_create_request.py new file mode 100644 index 0000000..6318099 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/total_create_request.py @@ -0,0 +1,27 @@ +# 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 TotalCreateRequest(BaseModel): + model_config = ConfigDict( + extra="allow", + ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/total_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/total_update_request.py new file mode 100644 index 0000000..e8ca037 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/total_update_request.py @@ -0,0 +1,27 @@ +# 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 TotalUpdateRequest(BaseModel): + model_config = ConfigDict( + extra="allow", + ) diff --git a/ucp b/ucp deleted file mode 160000 index cc50009..0000000 --- a/ucp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cc500091eb173ef6f1578cf8904b05015f6a7e06 From 95e1ed53616c62df3421dbcfd2df76eec0206190 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Fri, 6 Feb 2026 11:51:01 +0000 Subject: [PATCH 6/7] fix schema generation --- preprocess_schemas.py | 20 +++++++++++-------- .../types/fulfillment_create_request.py | 4 ++-- .../fulfillment_method_create_request.py | 4 ++-- .../fulfillment_method_update_request.py | 4 ++-- .../types/fulfillment_update_request.py | 4 ++-- .../types/line_item_create_request.py | 4 ++-- .../types/line_item_update_request.py | 4 ++-- ucp | 1 + 8 files changed, 25 insertions(+), 20 deletions(-) create mode 160000 ucp diff --git a/preprocess_schemas.py b/preprocess_schemas.py index fc15eb1..2d8e802 100644 --- a/preprocess_schemas.py +++ b/preprocess_schemas.py @@ -19,6 +19,7 @@ import json import shutil import copy +import os from pathlib import Path from typing import Any, Dict @@ -159,11 +160,12 @@ def update_refs_for_scenario(obj: Any, scenario: str, schemas_with_scenarios: se elif value.endswith(".json"): # Resolve the relative path from current schema's directory if current_dir: - ref_path = str(Path(current_dir) / value) + combined = os.path.join(current_dir, value) + ref_path = os.path.normpath(combined) else: - ref_path = value + ref_path = os.path.normpath(value) - # Normalize the path + # Normalize the path (force forward slashes) ref_path = ref_path.replace("\\", "/") # Check if the referenced schema has scenarios @@ -288,7 +290,7 @@ def clean_schema_for_codegen(schema: Dict[str, Any]) -> Dict[str, Any]: return schema -def preprocess_schema_file(input_path: Path, output_path: Path, schemas_with_scenarios: set) -> None: +def preprocess_schema_file(input_path: Path, output_path: Path, schemas_with_scenarios: set, schema_dir: str) -> None: """Preprocess a single schema file.""" with open(input_path, 'r', encoding='utf-8') as f: schema = json.load(f) @@ -318,9 +320,6 @@ def preprocess_schema_file(input_path: Path, output_path: Path, schemas_with_sce # Generate scenario-specific schemas base_name = output_path.stem # filename without extension - # Get the directory of this schema relative to the root (for resolving refs) - schema_dir = str(output_path.parent.relative_to(output_path.parent.parent.parent)) - scenarios = process_ucp_request_scenarios(schema, base_name, schemas_with_scenarios, schema_dir) # Ensure output directory exists @@ -383,8 +382,13 @@ def preprocess_schemas(input_dir: Path, output_dir: Path) -> None: rel_path = json_file.relative_to(input_dir) output_path = output_dir / rel_path + # Calculate directory of this file relative to valid root + file_rel_dir = str(rel_path.parent).replace("\\", "/") + if file_rel_dir == ".": + file_rel_dir = "" + print(f" Processing: {rel_path}") - preprocess_schema_file(json_file, output_path, schemas_with_scenarios) + preprocess_schema_file(json_file, output_path, schemas_with_scenarios, file_rel_dir) print(f"Preprocessing complete. Output in {output_dir}") diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_create_request.py index 6b3a605..14bdc77 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_create_request.py @@ -20,7 +20,7 @@ from pydantic import BaseModel, ConfigDict -from . import fulfillment_method +from . import fulfillment_method_create_request class FulfillmentCreateRequest(BaseModel): @@ -31,7 +31,7 @@ class FulfillmentCreateRequest(BaseModel): model_config = ConfigDict( extra="allow", ) - methods: list[fulfillment_method.FulfillmentMethod] | None = None + methods: list[fulfillment_method_create_request.FulfillmentMethodCreateRequest] | None = None """ Fulfillment methods for cart items. """ 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 8ee2ebc..399ef6f 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 @@ -22,7 +22,7 @@ from pydantic import BaseModel, ConfigDict -from . import fulfillment_destination, fulfillment_group +from . import fulfillment_destination, fulfillment_group_create_request class FulfillmentMethodCreateRequest(BaseModel): @@ -49,7 +49,7 @@ class FulfillmentMethodCreateRequest(BaseModel): """ ID of the selected destination. """ - groups: list[fulfillment_group.FulfillmentGroup] | None = None + groups: list[fulfillment_group_create_request.FulfillmentGroupCreateRequest] | None = None """ Fulfillment groups for selecting options. Agent sets selected_option_id on groups to choose shipping method. """ 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 e5796c2..4d1e810 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 @@ -20,7 +20,7 @@ from pydantic import BaseModel, ConfigDict -from . import fulfillment_destination, fulfillment_group +from . import fulfillment_destination, fulfillment_group_update_request class FulfillmentMethodUpdateRequest(BaseModel): @@ -47,7 +47,7 @@ class FulfillmentMethodUpdateRequest(BaseModel): """ ID of the selected destination. """ - groups: list[fulfillment_group.FulfillmentGroup] | None = None + groups: list[fulfillment_group_update_request.FulfillmentGroupUpdateRequest] | None = None """ Fulfillment groups for selecting options. Agent sets selected_option_id on groups to choose shipping method. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_update_request.py index 3422acb..7551337 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_update_request.py @@ -20,7 +20,7 @@ from pydantic import BaseModel, ConfigDict -from . import fulfillment_method +from . import fulfillment_method_update_request class FulfillmentUpdateRequest(BaseModel): @@ -31,7 +31,7 @@ class FulfillmentUpdateRequest(BaseModel): model_config = ConfigDict( extra="allow", ) - methods: list[fulfillment_method.FulfillmentMethod] | None = None + methods: list[fulfillment_method_update_request.FulfillmentMethodUpdateRequest] | None = None """ Fulfillment methods for cart items. """ 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 e760d41..754e4fe 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 @@ -20,7 +20,7 @@ from pydantic import BaseModel, ConfigDict, Field -from . import item as item_1 +from . import item_create_request class LineItemCreateRequest(BaseModel): @@ -31,7 +31,7 @@ class LineItemCreateRequest(BaseModel): model_config = ConfigDict( extra="allow", ) - item: item_1.Item + item: item_create_request.ItemCreateRequest quantity: int = Field(..., ge=1) """ Quantity of the item being purchased. 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 c85a360..fc43593 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 @@ -20,7 +20,7 @@ from pydantic import BaseModel, ConfigDict, Field -from . import item as item_1 +from . import item_update_request class LineItemUpdateRequest(BaseModel): @@ -32,7 +32,7 @@ class LineItemUpdateRequest(BaseModel): extra="allow", ) id: str | None = None - item: item_1.Item + item: item_update_request.ItemUpdateRequest quantity: int = Field(..., ge=1) """ Quantity of the item being purchased. diff --git a/ucp b/ucp new file mode 160000 index 0000000..cc50009 --- /dev/null +++ b/ucp @@ -0,0 +1 @@ +Subproject commit cc500091eb173ef6f1578cf8904b05015f6a7e06 From c487f9809d2b0ff26731ada9121b946141d3293a Mon Sep 17 00:00:00 2001 From: Federico D'Amato Date: Fri, 6 Feb 2026 19:07:17 +0100 Subject: [PATCH 7/7] Update pyproject.toml --- pyproject.toml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 78695e0..6cd8fea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,11 @@ [project] name = "ucp-sdk" -version = "0.1.0" +version = "0.2.0" description = "UCP Python SDK" readme = "README.md" authors = [ - { name = "Florin Iucha", email = "fiucha@google.com" } + { name = "Florin Iucha", email = "fiucha@google.com" }, + { name = "Federico D'Amato", email = "damaz@google.com" } ] requires-python = ">=3.10" dependencies = [ @@ -43,4 +44,4 @@ custom-file-header = """ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable -""" \ No newline at end of file +"""