diff --git a/README.md b/README.md index 6a04f4b..0a11848 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 @@ -62,9 +59,13 @@ To regenerate the models: ```bash uv sync -./generate_models.sh +./generate_models.sh ``` +Where `` is the version of the UCP specification to use (for example, "2026-01-23"). + +If no version is specified, the `main` branch of the [UCP repo](https://github.com/Universal-Commerce-Protocol/ucp) will be used. + The generated code is automatically formatted using `ruff`. ## Contributing diff --git a/generate_models.sh b/generate_models.sh index 26afb35..e99e9e0 100755 --- a/generate_models.sh +++ b/generate_models.sh @@ -4,13 +4,38 @@ # Ensure we are in the script's directory cd "$(dirname "$0")" || exit +# Add ~/.local/bin to PATH for uv +export PATH="$HOME/.local/bin:$PATH" + +# Check if git is installed +if ! command -v git &> /dev/null; then + echo "Error: git not found. Please install git." + exit 1 +fi + +# UCP Version to use (if provided, use release/$1 branch; otherwise, use main) +if [ -z "$1" ]; then + BRANCH="main" + echo "No version specified, cloning main branch..." +else + BRANCH="release/$1" + echo "Cloning version $1 (branch: $BRANCH)..." +fi + +# Ensure ucp directory is clean before cloning +rm -rf ucp +git clone -b "$BRANCH" --depth 1 https://github.com/Universal-Commerce-Protocol/ucp ucp + # Output directory -OUTPUT_DIR="src/ucp_sdk/models" +OUTPUT_DIR="src/ucp_sdk/models/schemas" # Schema directory (relative to this script) -SCHEMA_DIR="../../spec/" +SCHEMA_DIR="ucp/source/schemas" + +echo "Preprocessing schemas..." +uv run python preprocess_schemas.py -echo "Generating Pydantic models from $SCHEMA_DIR..." +echo "Generating Pydantic models from preprocessed schemas..." # Check if uv is installed if ! command -v uv &> /dev/null; then @@ -23,9 +48,11 @@ fi rm -r -f "$OUTPUT_DIR" mkdir -p "$OUTPUT_DIR" + # 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: Formatting is done as a post-processing step. uv run \ --link-mode=copy \ --extra-index-url https://pypi.org/simple python \ @@ -42,6 +69,13 @@ uv run \ --use-double-quotes \ --no-use-annotated \ --allow-extra-fields \ - --formatters ruff-format ruff-check + --custom-template-dir templates \ + --additional-imports pydantic.ConfigDict + + +echo "Formatting generated models..." +uv run ruff format +uv run ruff check --fix "$OUTPUT_DIR" 2>&1 | grep -E "^(All checks passed|Fixed|Found)" || echo "Formatting complete" + echo "Done. Models generated in $OUTPUT_DIR" diff --git a/preprocess_schemas.py b/preprocess_schemas.py new file mode 100644 index 0000000..9fe5ed0 --- /dev/null +++ b/preprocess_schemas.py @@ -0,0 +1,533 @@ +# 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. + +import json +import copy +from pathlib import Path +import sys + +# --- I/O Helpers --- + + +def load_json(path): + """Loads JSON data from a file.""" + with open(path, "r") as f: + return json.load(f) + + +def save_json(data, path): + """Saves data to a JSON file with standard indentation.""" + with open(path, "w") as f: + json.dump(data, f, indent=2) + + +# --- Traversal Helper --- + + +def iter_nodes(root): + """ + Iteratively yields all dictionary or list nodes in a JSON tree. + This replaces multiple manual stack-walking implementations. + """ + stack = [root] + visited = {id(root)} + while stack: + curr = stack.pop() + yield curr + + # Identify children for the next iteration + children = [] + if isinstance(curr, dict): + children = curr.values() + elif isinstance(curr, list): + children = curr + + for child in children: + if isinstance(child, (dict, list)) and id(child) not in visited: + visited.add(id(child)) + stack.append(child) + + +# --- Reference Resolution --- + + +def resolve_local_ref(ref, root): + """ + Resolves a local JSON pointer (e.g., #/$defs/name) within the same document. + Returns the resolved schema fragment or None if invalid. + """ + if not isinstance(ref, str) or not ref.startswith("#/"): + return None + + parts = ref.split("/") + current = root + for part in parts[1:]: + if isinstance(current, dict) and part in current: + current = current[part] + elif isinstance(current, list) and part.isdigit(): + idx = int(part) + if 0 <= idx < len(current): + current = current[idx] + else: + return None + else: + return None + return current + + +# --- Schema Normalization and Flattening --- + + +def merge_all_of_to_node(node, root): + """ + Merges 'allOf' components into the node itself. + + RATIONALE: Code generators (like datamodel-codegen) often create cleaner Pydantic + models if inheritance is flattened at the schema level rather than relying on + complex 'allOf' chains which can lead to redundant intermediate classes. + """ + if "allOf" not in node: + return + + all_of_sources = node.pop("allOf") + merged_properties = {} + merged_required = [] + poly_branches = {} + remaining_refs = [] + + for item in all_of_sources: + # Resolve local references (internal inheritance) before merging + if isinstance(item, dict) and "$ref" in item: + resolved = resolve_local_ref(item["$ref"], root) + if resolved: + item = copy.deepcopy(resolved) + else: + # Keep external refs; we'll handle them in variant generation if needed + remaining_refs.append(item) + continue + + if not isinstance(item, dict): + continue + + # Extract polymorphic branches (anyOf, oneOf) to keep the node flat + for poly_key in ["anyOf", "oneOf"]: + if poly_key in item: + poly_branches.setdefault(poly_key, []).extend( + item.pop(poly_key) + ) + + # Merge core property definitions and requirements + if "properties" in item: + merged_properties.update(item["properties"]) + if "required" in item: + for req in item["required"]: + if req not in merged_required: + merged_required.append(req) + + # Carry over generic metadata (title, description, etc.) if not defined in the base + for k, v in item.items(): + if ( + k + not in [ + "properties", + "required", + "allOf", + "$ref", + "anyOf", + "oneOf", + ] + and k not in node + ): + node[k] = v + + # Apply merged state back to the primary node + if merged_properties: + node.setdefault("properties", {}).update(merged_properties) + + if merged_required: + existing = node.setdefault("required", []) + for r in merged_required: + if r not in existing: + existing.append(r) + + # Re-insert any combined polymorphic branches + for k, branches in poly_branches.items(): + node.setdefault(k, []).extend(branches) + + # If some refs couldn't be resolved locally, put them back into a slim allOf + if remaining_refs: + node["allOf"] = remaining_refs + + +def distribute_properties_to_branches(node): + """ + Inherits base properties/requirements into anyOf/oneOf branches. + + RATIONALE: This ensures that each branch of a union is a self-contained, valid + model in Pydantic. Without this, a generated union model might miss required + common fields if it's treated as a pure 'oneOf' alternative. + """ + if "properties" not in node: + return + + base_props = node["properties"] + base_req = node.get("required", []) + base_type = node.get("type") + + for poly_key in ["anyOf", "oneOf"]: + if poly_key not in node: + continue + + updated_branches = [] + for branch in node[poly_key]: + if not isinstance(branch, dict): + updated_branches.append(branch) + continue + + # Branch properties override common base properties + new_branch = copy.deepcopy(branch) + branch_props = new_branch.setdefault("properties", {}) + combined_props = copy.deepcopy(base_props) + combined_props.update(branch_props) + new_branch["properties"] = combined_props + + # Combine union and base required field lists + new_branch["required"] = list( + set(base_req + new_branch.get("required", [])) + ) + + # Ensure the branch knows its JSON type (usually 'object') if inheriting from common base + if "type" not in new_branch and base_type: + new_branch["type"] = base_type + + updated_branches.append(new_branch) + node[poly_key] = updated_branches + + +def flatten_entity_reference(node, entity_definition): + """ + Replaces $ref to 'ucp.json#/$defs/entity' with actual logic. + This effectively converts 'Entity' inheritance into direct 'BaseModel' fields. + """ + if "allOf" not in node or not entity_definition: + return + + filtered_all_of = [] + for item in node["allOf"]: + is_entity_ref = isinstance(item, dict) and item.get( + "$ref", "" + ).endswith("ucp.json#/$defs/entity") + if is_entity_ref: + # Inline a copy; strip name to prevent unwanted class generation for the base + e_copy = copy.deepcopy(entity_definition) + e_copy.pop("title", None) + e_copy.pop("description", None) + filtered_all_of.append(e_copy) + else: + filtered_all_of.append(item) + node["allOf"] = filtered_all_of + + +def preprocess_full_schema(schema, entity_def=None): + """ + Main entry point for normalizing a single schema file. + Uses bottom-up iteration to ensure nested structures are flat before parents process them. + """ + # 1. Discovery: find all dictionaries in the tree + nodes = [n for n in iter_nodes(schema) if isinstance(n, dict)] + + # 2. Execution: process in reverse (approximate bottom-to-top) + for node in reversed(nodes): + if entity_def: + flatten_entity_reference(node, entity_def) + merge_all_of_to_node(node, schema) + distribute_properties_to_branches(node) + + +# --- Variant Generation (Create/Update/Complete) --- + + +def get_required_ops(schema): + """ + Scans a schema for the custom 'ucp_request' metadata. + Returns a set of operation keys (e.g. {'create', 'update'}) that need distinct models. + """ + ops = set() + properties = schema.get("properties", {}) + if not isinstance(properties, dict): + return ops + + for data in properties.values(): + if isinstance(data, dict): + marker = data.get("ucp_request") + if isinstance(marker, str): + ops.update(["create", "update"]) # Standard shortcut + elif isinstance(marker, dict): + ops.update(marker.keys()) + return ops + + +def eval_prop_inclusion(name, data, op, base_required): + """ + Decides if a property should be included or required for a specific operation. + Follows UCP 'ucp_request' metadata rules. + """ + if not isinstance(data, dict): + return True, name in base_required + + marker = data.get("ucp_request") + include = True + is_required = name in base_required + + if marker == "omit": + include = False + elif marker == "required": + is_required = True + elif isinstance(marker, dict): + val = marker.get(op) + if val == "omit" or val is None: + include = False + elif val == "required": + is_required = True + + return include, is_required + + +def update_variant_identity(variant_schema, op, stem): + """Updates title and $id so that generated code doesn't have naming collisions.""" + base_title = variant_schema.get("title", stem) + variant_schema["title"] = f"{base_title} {op.capitalize()} Request" + + if "$id" in variant_schema: + old_id = variant_schema["$id"] + if "/" in old_id: + parts = old_id.split("/") + # Support both .json and extension-less IDs + name_ext = parts[-1].split(".", 1) + name = name_ext[0] + ext = name_ext[1] if len(name_ext) > 1 else "json" + + parts[-1] = f"{name}_{op}_request.{ext}" + variant_schema["$id"] = "/".join(parts) + + +def rewrite_refs_to_variants(root, op, file_path, variant_needs): + """ + Walks a schema tree and updates external links to point to variant files. + Example: product.json -> product_create_request.json + """ + for node in iter_nodes(root): + if isinstance(node, dict) and "$ref" in node: + ref = node["$ref"] + if "#" not in ref: # External file reference + abs_target = (file_path.parent / ref).resolve() + if ( + str(abs_target) in variant_needs + and op in variant_needs[str(abs_target)] + ): + ref_path = Path(ref) + node["$ref"] = str( + ref_path.parent / f"{ref_path.stem}_{op}_request.json" + ) + + +def generate_variants(path, schema, ops, all_variant_needs): + """Creates specific JSON files (create/update/complete) based on ucp_request markers.""" + file_path = Path(path) + for op in ops: + variant = copy.deepcopy(schema) + update_variant_identity(variant, op, file_path.stem) + + new_props = {} + new_required = [] + base_req = schema.get("required", []) + + for name, data in schema.get("properties", {}).items(): + include, required = eval_prop_inclusion(name, data, op, base_req) + if include: + prop_data = copy.deepcopy(data) + if isinstance(prop_data, dict): + prop_data.pop("ucp_request", None) + rewrite_refs_to_variants( + prop_data, op, file_path, all_variant_needs + ) + + new_props[name] = prop_data + if required: + new_required.append(name) + + variant["properties"] = new_props + variant["required"] = new_required + + out = file_path.parent / f"{file_path.stem}_{op}_request.json" + save_json(variant, out) + print(f"Generated variant: {out}") + + +# --- Global Normalization --- + + +def fix_metadata_structure(schema_dir): + """ + Ensures ucp.json has a root union and other files point to it generically. + This enables a unified 'ucp' metadata property across the entire SDK. + """ + ucp_path = schema_dir / "ucp.json" + if not ucp_path.exists(): + return + + ucp = load_json(ucp_path) + ucp["oneOf"] = [ + {"$ref": f"#/$defs/{d}"} + for d in [ + "platform_schema", + "business_schema", + "response_checkout_schema", + "response_order_schema", + "response_cart_schema", + ] + ] + save_json(ucp, ucp_path) + + for f in schema_dir.rglob("*.json"): + if f.name == "ucp.json" or "_request.json" in f.name: + continue + try: + s = load_json(f) + # Find the 'ucp' property and point it to the ucp.json root + ucp_prop = s.get("properties", {}).get("ucp", {}) + if ( + isinstance(ucp_prop, dict) + and "$ref" in ucp_prop + and "ucp.json" in ucp_prop["$ref"] + ): + ucp_prop["$ref"] = ucp_prop["$ref"].split("#")[0] + save_json(s, f) + except: + continue + + +# --- Dependency Management --- + + +def extract_external_refs(schema, path): + """Finds all relative external file references in the schema properties.""" + refs = [] + props = schema.get("properties", {}) + if not isinstance(props, dict): + return refs + + for name, data in props.items(): + for node in iter_nodes(data): + if isinstance(node, dict) and "$ref" in node: + ref = node["$ref"] + if "#" not in ref: + abs_path = str((path.parent / ref).resolve()) + refs.append((name, abs_path)) + return refs + + +def propagate_needs_transitive(variant_needs, schema_refs, schemas): + """ + If a parent model needs a 'create' variant, and it has a child property 'X', + then 'X' also needs a 'create' variant so that references match. + """ + changed = True + while changed: + changed = False + for path, refs in schema_refs.items(): + if path not in variant_needs: + continue + + for op in list(variant_needs[path]): + for prop_name, child_path in refs: + if child_path not in schemas: + continue + + # Only propagate if the property isn't 'omit'ted for this op + data = ( + schemas[path].get("properties", {}).get(prop_name, {}) + ) + include, _ = eval_prop_inclusion( + prop_name, data, op, schemas[path].get("required", []) + ) + + if include: + target_set = variant_needs.setdefault(child_path, set()) + if op not in target_set: + target_set.add(op) + changed = True + + +# --- Main Flow --- + + +def main(): + """ + Orchestrates the schema preprocessing pipeline: + 1. metadata normalization: unifies ucp properties + 2. Pass 1: Local flattening (allOf) and discovery of needed variants + 3. Pass 2: Transitive propagation (ensuring matched variants for linked schemas) + 4. Pass 3: Variant file generation (*_request.json) + """ + target_dir = Path( + sys.argv[1] if len(sys.argv) > 1 else "ucp/source/schemas" + ) + if not target_dir.exists(): + print(f"Error: Directory {target_dir} not found.") + return + + # Phase 0: Ensure the metadata 'ucp' property is consistent across all files + fix_metadata_structure(target_dir) + + # Load base entity definition for inlining (flattening inheritance) + ucp_path = target_dir / "ucp.json" + entity_def = ( + load_json(ucp_path).get("$defs", {}).get("entity", {}) + if ucp_path.exists() + else {} + ) + + schemas, schema_refs, variant_needs = {}, {}, {} + + # Pass 1: Load every schema, flatten it locally, and find explicit variant markers + for f in target_dir.rglob("*.json"): + if "_request.json" in f.name: + continue + try: + s = load_json(f) + preprocess_full_schema(s, entity_def) + save_json(s, f) # Write back the flattened core schema + + p_abs = str(f.resolve()) + schemas[p_abs] = s + schema_refs[p_abs] = extract_external_refs(s, f) + + # Check if this schema explicitly asks for variants via 'ucp_request' markers + ops = get_required_ops(s) + if ops: + variant_needs[p_abs] = ops + except Exception as e: + print(f"Failed to process {f}: {e}") + + # Pass 2: Propagate the need for variants down the dependency tree + propagate_needs_transitive(variant_needs, schema_refs, schemas) + + # Pass 3: Finally write out the new variant files + for path, ops in variant_needs.items(): + generate_variants(path, schemas[path], ops, variant_needs) + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml index d4d858a..566418e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,12 @@ [project] name = "ucp-sdk" -version = "0.1.0" +version = "0.0.3" description = "UCP Python SDK" readme = "README.md" +license-files = ["LICENSE"] 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 = [ @@ -47,19 +49,22 @@ custom-file-header = """ [tool.ruff] line-length = 80 -indent-width = 2 +indent-width = 4 [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"] +ignore = ["D212", "D200"] + +[tool.ruff.lint.pydocstyle] +convention = "google" [tool.ruff.lint.isort] combine-as-imports = true force-sort-within-sections = true -case-sensitive = 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/__init__.py b/src/ucp_sdk/models/discovery/__init__.py deleted file mode 100644 index 1252d6b..0000000 --- a/src/ucp_sdk/models/discovery/__init__.py +++ /dev/null @@ -1,17 +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 diff --git a/src/ucp_sdk/models/discovery/profile_schema.py b/src/ucp_sdk/models/discovery/profile_schema.py deleted file mode 100644 index bef9040..0000000 --- a/src/ucp_sdk/models/discovery/profile_schema.py +++ /dev/null @@ -1,95 +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 ..schemas.shopping.types import payment_handler_resp -from .._internal import DiscoveryProfile - - -class SigningKey(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - kid: str - """ - Key ID. Referenced in signature headers to identify which key to use for verification. - """ - kty: str - """ - Key type (e.g., 'EC', 'RSA'). - """ - crv: str | None = None - """ - Curve name for EC keys (e.g., 'P-256'). - """ - x: str | None = None - """ - X coordinate for EC public keys (base64url encoded). - """ - y: str | None = None - """ - Y coordinate for EC public keys (base64url encoded). - """ - n: str | None = None - """ - Modulus for RSA public keys (base64url encoded). - """ - e: str | None = None - """ - Exponent for RSA public keys (base64url encoded). - """ - use: Literal["sig", "enc"] | None = None - """ - Key usage. Should be 'sig' for signing keys. - """ - alg: str | None = None - """ - Algorithm (e.g., 'ES256', 'RS256'). - """ - - -class Payment(BaseModel): - """Payment configuration containing handlers.""" - - 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(BaseModel): - """Schema for UCP discovery profile returned from /.well-known/ucp.""" - - model_config = ConfigDict( - extra="allow", - ) - ucp: DiscoveryProfile - payment: Payment | None = None - """ - Payment configuration containing handlers - """ - 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/__init__.py b/src/ucp_sdk/models/schemas/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/schemas/__init__.py +++ b/src/ucp_sdk/models/schemas/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/capability.py b/src/ucp_sdk/models/schemas/capability.py index 26dc3d7..3e326ff 100644 --- a/src/ucp_sdk/models/schemas/capability.py +++ b/src/ucp_sdk/models/schemas/capability.py @@ -18,6 +18,163 @@ from __future__ import annotations -from .._internal import Base, Discovery, Response, UcpCapability +from typing import Any -__all__ = ["Base", "Discovery", "Response", "UcpCapability"] +from pydantic import AnyUrl, BaseModel, ConfigDict, Field, RootModel + + +class UcpCapability(RootModel[Any]): + model_config = ConfigDict( + frozen=True, + ) + 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 Version(RootModel[Any]): + model_config = ConfigDict( + frozen=True, + ) + root: Any + + +class Base(BaseModel): + 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. + """ + 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. + """ + + +class PlatformSchema(BaseModel): + """ + Full capability declaration for platform-level discovery. Includes spec/schema URLs for agent fetching. + """ + + model_config = ConfigDict( + extra="allow", + ) + version: Version + """ + Entity version in YYYY-MM-DD format. + """ + spec: AnyUrl + """ + URL to human-readable specification document. + """ + schema_: AnyUrl = Field(..., 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. + """ + 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. + """ + + +class BusinessSchema(BaseModel): + """ + Capability configuration for business/merchant level. May include business-specific config overrides. + """ + + 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. + """ + 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. + """ + + +class ResponseSchema(BaseModel): + """ + Capability reference in responses. Only name/version required to confirm active capabilities. + """ + + 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. + """ + 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. + """ 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..c51161c --- /dev/null +++ b/src/ucp_sdk/models/schemas/payment_handler.py @@ -0,0 +1,156 @@ +# 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 PaymentHandler(RootModel[Any]): + model_config = ConfigDict( + frozen=True, + ) + root: Any = Field(..., title="Payment Handler") + """ + Schema for UCP payment handlers. Handlers define how payment instruments are processed. + """ + + +class Version(RootModel[Any]): + model_config = ConfigDict( + frozen=True, + ) + root: Any + + +class Base(BaseModel): + 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 + """ + 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): + """ + Platform declaration for discovery profiles. May include partial config state required for discovery. + """ + + model_config = ConfigDict( + extra="allow", + ) + version: Version + """ + Entity version in YYYY-MM-DD format. + """ + spec: AnyUrl + """ + URL to human-readable specification document. + """ + schema_: AnyUrl = Field(..., alias="schema") + """ + URL to JSON Schema defining this entity's structure and payloads. + """ + id: str + """ + 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 BusinessSchema(BaseModel): + """ + Business declaration for discovery profiles. May include partial config state required for discovery. + """ + + 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 + """ + 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 ResponseSchema(BaseModel): + """ + Handler reference in responses. May include full config state for runtime usage of the handler. + """ + + 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 + """ + 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. + """ diff --git a/src/ucp_sdk/models/schemas/service.py b/src/ucp_sdk/models/schemas/service.py new file mode 100644 index 0000000..2a91c36 --- /dev/null +++ b/src/ucp_sdk/models/schemas/service.py @@ -0,0 +1,592 @@ +# 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, BaseModel, ConfigDict, Field, RootModel + + +class UcpService(RootModel[Any]): + model_config = ConfigDict( + frozen=True, + ) + root: Any = Field(..., title="UCP Service") + """ + Service binding for a specific transport. Each transport binding is a separate entry in the service array. + """ + + +class Config(BaseModel): + """ + Entity-specific configuration. Structure defined by each entity's schema. + """ + + 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 Version(RootModel[Any]): + model_config = ConfigDict( + frozen=True, + ) + root: Any + + +class Base(BaseModel): + 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. + """ + transport: Literal["rest", "mcp", "a2a", "embedded"] + """ + Transport protocol for this service binding. + """ + endpoint: AnyUrl | None = None + """ + Endpoint URL for this transport binding. + """ + + +class PlatformSchema(BaseModel): + """ + Full service declaration for platform-level discovery. Different transports require different fields. + """ + + model_config = ConfigDict( + extra="allow", + ) + version: Version + """ + Entity version in YYYY-MM-DD format. + """ + spec: AnyUrl + """ + URL to human-readable specification document. + """ + schema_: AnyUrl = Field(..., 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. + """ + transport: Literal["rest"] + """ + Transport protocol for this service binding. + """ + endpoint: AnyUrl + """ + Endpoint URL for this transport binding. + """ + + +class PlatformSchema5(BaseModel): + """ + Full service declaration for platform-level discovery. Different transports require different fields. + """ + + model_config = ConfigDict( + extra="allow", + ) + version: Version + """ + Entity version in YYYY-MM-DD format. + """ + spec: AnyUrl + """ + URL to human-readable specification document. + """ + schema_: AnyUrl = Field(..., 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. + """ + transport: Literal["mcp"] + """ + Transport protocol for this service binding. + """ + endpoint: AnyUrl + """ + Endpoint URL for this transport binding. + """ + + +class PlatformSchema6(BaseModel): + """ + Full service declaration for platform-level discovery. Different transports require different fields. + """ + + model_config = ConfigDict( + extra="allow", + ) + version: Version + """ + Entity version in YYYY-MM-DD format. + """ + spec: AnyUrl + """ + 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. + """ + transport: Literal["a2a"] + """ + Transport protocol for this service binding. + """ + endpoint: AnyUrl + """ + Endpoint URL for this transport binding. + """ + + +class PlatformSchema7(BaseModel): + """ + Full service declaration for platform-level discovery. Different transports require different fields. + """ + + model_config = ConfigDict( + extra="allow", + ) + version: Version + """ + Entity version in YYYY-MM-DD format. + """ + spec: AnyUrl + """ + URL to human-readable specification document. + """ + schema_: AnyUrl = Field(..., 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. + """ + transport: Literal["embedded"] + """ + Transport protocol for this service binding. + """ + endpoint: AnyUrl | None = None + """ + Endpoint URL for this transport binding. + """ + + +class PlatformSchema3( + RootModel[ + PlatformSchema | PlatformSchema5 | PlatformSchema6 | PlatformSchema7 + ] +): + model_config = ConfigDict( + frozen=True, + ) + 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", + ) + 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. + """ + transport: Literal["rest"] + """ + Transport protocol for this service binding. + """ + endpoint: AnyUrl + """ + Endpoint URL for this transport binding. + """ + + +class BusinessSchema4(BaseModel): + """ + Service binding for business/merchant configuration. May override platform endpoints. + """ + + 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. + """ + transport: Literal["mcp"] + """ + Transport protocol for this service binding. + """ + endpoint: AnyUrl + """ + Endpoint URL for this transport binding. + """ + + +class BusinessSchema5(BaseModel): + """ + Service binding for business/merchant configuration. May override platform endpoints. + """ + + 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. + """ + transport: Literal["a2a"] + """ + Transport protocol for this service binding. + """ + endpoint: AnyUrl + """ + Endpoint URL for this transport binding. + """ + + +class BusinessSchema6(BaseModel): + """ + Service binding for business/merchant configuration. May override platform endpoints. + """ + + 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: Config | None = Field(None, title="Embedded Transport Config") + """ + Entity-specific configuration. Structure defined by each entity's schema. + """ + transport: Literal["embedded"] + """ + Transport protocol for this service binding. + """ + endpoint: AnyUrl | None = None + """ + Endpoint URL for this transport binding. + """ + + +class BusinessSchema2( + RootModel[ + BusinessSchema | BusinessSchema4 | BusinessSchema5 | BusinessSchema6 + ] +): + model_config = ConfigDict( + frozen=True, + ) + 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", + ) + 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. + """ + transport: Literal["rest"] + """ + Transport protocol for this service binding. + """ + endpoint: AnyUrl | None = None + """ + Endpoint URL for this transport binding. + """ + + +class ResponseSchema4(BaseModel): + """ + Service binding in API responses. Includes per-resource transport configuration via typed config. + """ + + 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. + """ + transport: Literal["mcp"] + """ + Transport protocol for this service binding. + """ + endpoint: AnyUrl | None = None + """ + Endpoint URL for this transport binding. + """ + + +class ResponseSchema5(BaseModel): + """ + Service binding in API responses. Includes per-resource transport configuration via typed config. + """ + + 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. + """ + transport: Literal["a2a"] + """ + Transport protocol for this service binding. + """ + endpoint: AnyUrl | None = None + """ + Endpoint URL for this transport binding. + """ + + +class ResponseSchema6(BaseModel): + """ + Service binding in API responses. Includes per-resource transport configuration via typed config. + """ + + 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: Config | None = Field(None, title="Embedded Transport Config") + """ + Entity-specific configuration. Structure defined by each entity's schema. + """ + transport: Literal["embedded"] + """ + Transport protocol for this service binding. + """ + endpoint: AnyUrl | None = None + """ + Endpoint URL for this transport binding. + """ + + +class ResponseSchema2( + RootModel[ + ResponseSchema | ResponseSchema4 | ResponseSchema5 | ResponseSchema6 + ] +): + model_config = ConfigDict( + frozen=True, + ) + root: ( + ResponseSchema | ResponseSchema4 | ResponseSchema5 | ResponseSchema6 + ) = Field(..., title="Service (Response Schema)") + """ + Service binding in API responses. Includes per-resource transport configuration via typed config. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/__init__.py b/src/ucp_sdk/models/schemas/shopping/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/schemas/shopping/__init__.py +++ b/src/ucp_sdk/models/schemas/shopping/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/shopping/ap2_mandate.py b/src/ucp_sdk/models/schemas/shopping/ap2_mandate.py index a41ef4c..a76c1d8 100644 --- a/src/ucp_sdk/models/schemas/shopping/ap2_mandate.py +++ b/src/ucp_sdk/models/schemas/shopping/ap2_mandate.py @@ -19,109 +19,135 @@ from __future__ import annotations from typing import Any, Literal + from pydantic import BaseModel, ConfigDict, Field, RootModel -from .checkout_resp import CheckoutResponse + +from .checkout import Checkout as Checkout_1 class Ap2MandateExtension(RootModel[Any]): - root: Any = Field(..., title="AP2 Mandate Extension") - """ + model_config = ConfigDict( + frozen=True, + ) + 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", - ) - """ + model_config = ConfigDict( + frozen=True, + ) + 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", - ) - """ + model_config = ConfigDict( + frozen=True, + ) + 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: MerchantAuthorization | 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 | None = None + """ + SD-JWT+kb proving user authorized this checkout. + """ + + +class Ap2(BaseModel): + """ + AP2 extension data including merchant authorization. + """ - model_config = ConfigDict( - extra="allow", - ) - checkout_mandate: CheckoutMandate - """ + model_config = ConfigDict( + extra="allow", + ) + merchant_authorization: MerchantAuthorization | None = None + """ + Merchant's signature proving checkout terms are authentic. + """ + checkout_mandate: CheckoutMandate | 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") - """ + model_config = ConfigDict( + frozen=True, + ) + 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. +class Checkout(Checkout_1): + """ + Checkout extended with AP2 mandate support. """ - -class CheckoutResponseWithAp2(CheckoutResponse): - """Checkout extended with AP2 embedded signature support.""" - - model_config = ConfigDict( - extra="allow", - ) - ap2: Ap2CheckoutResponse | None = None - """ + model_config = ConfigDict( + extra="allow", + ) + ap2: Ap2 | None = None + """ AP2 extension data including merchant authorization. """ diff --git a/src/ucp_sdk/models/schemas/shopping/buyer_consent_create_req.py b/src/ucp_sdk/models/schemas/shopping/buyer_consent.py similarity index 59% rename from src/ucp_sdk/models/schemas/shopping/buyer_consent_create_req.py rename to src/ucp_sdk/models/schemas/shopping/buyer_consent.py index f1d2dae..4162d93 100644 --- a/src/ucp_sdk/models/schemas/shopping/buyer_consent_create_req.py +++ b/src/ucp_sdk/models/schemas/shopping/buyer_consent.py @@ -19,61 +19,72 @@ from __future__ import annotations from typing import Any + from pydantic import BaseModel, ConfigDict, Field, RootModel + +from .checkout import Checkout as Checkout_1 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") - """ +class BuyerConsentExtension(RootModel[Any]): + model_config = ConfigDict( + frozen=True, + ) + 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 - """ + marketing: bool | None = None + """ Consent for marketing communications. """ - sale_of_data: bool | None = None - """ + sale_of_data: bool | None = None + """ Consent for selling data to third parties (CCPA). """ class Buyer(Buyer_1): - """Buyer object extended with consent tracking.""" + """ + Buyer object extended with consent tracking. + """ - model_config = ConfigDict( - extra="allow", - ) - consent: Consent | None = None - """ + model_config = ConfigDict( + extra="allow", + ) + consent: Consent | None = None + """ Consent tracking fields. """ -class Checkout(CheckoutCreateRequest): - """Checkout extended with consent tracking via buyer object.""" +class Checkout(Checkout_1): + """ + Checkout extended with consent tracking via buyer object. + """ - model_config = ConfigDict( - extra="allow", - ) - buyer: Buyer | None = None - """ + model_config = ConfigDict( + extra="allow", + ) + buyer: Buyer | None = None + """ Buyer with consent tracking. """ diff --git a/src/ucp_sdk/models/schemas/shopping/buyer_consent_resp.py b/src/ucp_sdk/models/schemas/shopping/buyer_consent_resp.py deleted file mode 100644 index f2117fb..0000000 --- a/src/ucp_sdk/models/schemas/shopping/buyer_consent_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 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") - """ - 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(CheckoutResponse): - """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/checkout_resp.py b/src/ucp_sdk/models/schemas/shopping/checkout.py similarity index 55% rename from src/ucp_sdk/models/schemas/shopping/checkout_resp.py rename to src/ucp_sdk/models/schemas/shopping/checkout.py index 46085e3..e2c95a2 100644 --- a/src/ucp_sdk/models/schemas/shopping/checkout_resp.py +++ b/src/ucp_sdk/models/schemas/shopping/checkout.py @@ -19,75 +19,75 @@ 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 + +from .. import ucp as ucp_1 +from . import payment as payment_1 +from .types import buyer as buyer_1 +from .types import context as context_1 +from .types import line_item, link, message, order_confirmation, total -class CheckoutResponse(BaseModel): - """Base checkout schema. Extensions compose onto this using allOf.""" +class Checkout(BaseModel): + """ + Base checkout schema. Extensions compose onto this using allOf. + """ - model_config = ConfigDict( - extra="allow", - ) - ucp: ResponseCheckout - id: str - """ + model_config = ConfigDict( + extra="allow", + ) + ucp: ucp_1.UcpMetadata + id: str + """ Unique identifier of the checkout session. """ - line_items: list[line_item_resp.LineItemResponse] - """ + line_items: list[line_item.LineItem] + """ List of line items being checked out. """ - buyer: buyer_1.Buyer | None = None - """ + buyer: buyer_1.Buyer | None = None + """ Representation of the buyer. """ - status: Literal[ - "incomplete", - "requires_escalation", - "ready_for_complete", - "complete_in_progress", - "completed", - "canceled", - ] - """ + context: context_1.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. + 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] """ - totals: list[total_resp.TotalResponse] - """ Different cart totals. """ - messages: list[message.Message] | None = None - """ + messages: list[message.Message] | None = None + """ List of messages with error and info about the checkout session state. """ - links: list[link.Link] - """ + links: list[link.Link] + """ Links to be displayed by the platform (Privacy Policy, TOS). Mandatory for legal compliance. """ - expires_at: AwareDatetime | None = None - """ + expires_at: AwareDatetime | None = None + """ RFC 3339 expiry timestamp. Default TTL is 6 hours from creation if not sent. """ - continue_url: AnyUrl | None = None - """ + 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 - """ + payment: payment_1.Payment | None = None + 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_complete_request.py b/src/ucp_sdk/models/schemas/shopping/checkout_complete_request.py new file mode 100644 index 0000000..84dab14 --- /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_complete_request + + +class CheckoutCompleteRequest(BaseModel): + """ + Base checkout schema. Extensions compose onto this using allOf. + """ + + model_config = ConfigDict( + extra="allow", + ) + payment: payment_complete_request.PaymentCompleteRequest diff --git a/src/ucp_sdk/models/schemas/shopping/checkout_create_req.py b/src/ucp_sdk/models/schemas/shopping/checkout_create_request.py similarity index 60% rename from src/ucp_sdk/models/schemas/shopping/checkout_create_req.py rename to src/ucp_sdk/models/schemas/shopping/checkout_create_request.py index ad53d1f..1747b92 100644 --- a/src/ucp_sdk/models/schemas/shopping/checkout_create_req.py +++ b/src/ucp_sdk/models/schemas/shopping/checkout_create_request.py @@ -19,26 +19,30 @@ 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 + +from . import payment_create_request +from .types import ( + buyer_create_request, + context_create_request, + line_item_create_request, +) class CheckoutCreateRequest(BaseModel): - """Base checkout schema. Extensions compose onto this using allOf.""" + """ + Base checkout schema. Extensions compose onto this using allOf. + """ - model_config = ConfigDict( - extra="allow", - ) - line_items: list[line_item_create_req.LineItemCreateRequest] - """ + 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. + buyer: buyer_create_request.BuyerCreateRequest | None = None """ - currency: str - """ - ISO 4217 currency code. + Representation of the buyer. """ - payment: payment_create_req.PaymentCreateRequest + context: context_create_request.ContextCreateRequest | None = None + payment: payment_create_request.PaymentCreateRequest | None = None diff --git a/src/ucp_sdk/models/schemas/shopping/checkout_update_req.py b/src/ucp_sdk/models/schemas/shopping/checkout_update_request.py similarity index 61% rename from src/ucp_sdk/models/schemas/shopping/checkout_update_req.py rename to src/ucp_sdk/models/schemas/shopping/checkout_update_request.py index 6234448..4bc4d90 100644 --- a/src/ucp_sdk/models/schemas/shopping/checkout_update_req.py +++ b/src/ucp_sdk/models/schemas/shopping/checkout_update_request.py @@ -19,30 +19,34 @@ 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 + +from . import payment_update_request +from .types import ( + buyer_update_request, + context_update_request, + line_item_update_request, +) class CheckoutUpdateRequest(BaseModel): - """Base checkout schema. Extensions compose onto this using allOf.""" + """ + Base checkout schema. Extensions compose onto this using allOf. + """ - model_config = ConfigDict( - extra="allow", - ) - id: str - """ + model_config = ConfigDict( + extra="allow", + ) + id: str + """ Unique identifier of the checkout session. """ - line_items: list[line_item_update_req.LineItemUpdateRequest] - """ + 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. + buyer: buyer_update_request.BuyerUpdateRequest | None = None """ - currency: str - """ - ISO 4217 currency code. + Representation of the buyer. """ - payment: payment_update_req.PaymentUpdateRequest + context: context_update_request.ContextUpdateRequest | None = None + payment: payment_update_request.PaymentUpdateRequest | None = None diff --git a/src/ucp_sdk/models/schemas/shopping/discount_create_req.py b/src/ucp_sdk/models/schemas/shopping/discount.py similarity index 60% rename from src/ucp_sdk/models/schemas/shopping/discount_create_req.py rename to src/ucp_sdk/models/schemas/shopping/discount.py index 7595bb1..14e429b 100644 --- a/src/ucp_sdk/models/schemas/shopping/discount_create_req.py +++ b/src/ucp_sdk/models/schemas/shopping/discount.py @@ -19,89 +19,102 @@ from __future__ import annotations from typing import Any, Literal + from pydantic import BaseModel, ConfigDict, Field, RootModel -from .checkout_create_req import CheckoutCreateRequest + +from .checkout import Checkout as Checkout_1 -class DiscountExtensionCreateRequest(RootModel[Any]): - root: Any = Field(..., title="Discount Extension Create Request") - """ +class DiscountExtension(RootModel[Any]): + model_config = ConfigDict( + frozen=True, + ) + 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(CheckoutCreateRequest): - """Checkout extended with discount capability.""" +class Checkout(Checkout_1): + """ + Checkout extended with discount capability. + """ - model_config = ConfigDict( - extra="allow", - ) - discounts: DiscountsObject | None = None + model_config = ConfigDict( + extra="allow", + ) + discounts: DiscountsObject | None = None diff --git a/src/ucp_sdk/models/schemas/shopping/discount_resp.py b/src/ucp_sdk/models/schemas/shopping/discount_resp.py deleted file mode 100644 index 86fd012..0000000 --- a/src/ucp_sdk/models/schemas/shopping/discount_resp.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_resp import CheckoutResponse - - -class DiscountExtensionResponse(RootModel[Any]): - root: Any = Field(..., title="Discount Extension Response") - """ - 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(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_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..f926b0b --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/fulfillment/__init__.py @@ -0,0 +1,93 @@ +# 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 ..checkout import Checkout as Checkout_1 +from ..types import fulfillment as fulfillment_1 +from ..types import ( + fulfillment_available_method, + fulfillment_group, + fulfillment_method, + fulfillment_option, +) + + +class FulfillmentExtension(RootModel[Any]): + model_config = ConfigDict( + frozen=True, + ) + root: Any = Field(..., title="Fulfillment Extension") + """ + Extends Checkout with fulfillment support using methods, destinations, and groups. + """ + + +class FulfillmentAvailableMethod( + RootModel[fulfillment_available_method.FulfillmentAvailableMethod] +): + model_config = ConfigDict( + frozen=True, + ) + root: fulfillment_available_method.FulfillmentAvailableMethod + + +class FulfillmentOption(RootModel[fulfillment_option.FulfillmentOption]): + model_config = ConfigDict( + frozen=True, + ) + root: fulfillment_option.FulfillmentOption + + +class FulfillmentGroup(RootModel[fulfillment_group.FulfillmentGroup]): + model_config = ConfigDict( + frozen=True, + ) + root: fulfillment_group.FulfillmentGroup + + +class FulfillmentMethod(RootModel[fulfillment_method.FulfillmentMethod]): + model_config = ConfigDict( + frozen=True, + ) + root: fulfillment_method.FulfillmentMethod + + +class Fulfillment(RootModel[fulfillment_1.Fulfillment]): + model_config = ConfigDict( + frozen=True, + ) + root: fulfillment_1.Fulfillment + + +class Checkout(Checkout_1): + """ + Checkout extended with hierarchical fulfillment. + """ + + model_config = ConfigDict( + extra="allow", + ) + fulfillment: Fulfillment | None = None + """ + Fulfillment details. + """ diff --git a/src/ucp_sdk/models/handlers/tokenization/__init__.py b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/__init__.py similarity index 99% rename from src/ucp_sdk/models/handlers/tokenization/__init__.py rename to src/ucp_sdk/models/schemas/shopping/fulfillment/dev/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/handlers/tokenization/__init__.py +++ b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + diff --git a/src/ucp_sdk/models/__init__.py b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/__init__.py similarity index 99% rename from src/ucp_sdk/models/__init__.py rename to src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/__init__.py +++ b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + diff --git a/src/ucp_sdk/models/handlers/tokenization/openapi.py b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/shopping.py similarity index 82% rename from src/ucp_sdk/models/handlers/tokenization/openapi.py rename to src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/shopping.py index e8b3b8c..5a804e5 100644 --- a/src/ucp_sdk/models/handlers/tokenization/openapi.py +++ b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/shopping.py @@ -19,8 +19,12 @@ from __future__ import annotations from typing import Any -from pydantic import RootModel +from pydantic import ConfigDict, RootModel -class Model(RootModel[Any]): - root: Any + +class Fulfillment(RootModel[Any]): + model_config = ConfigDict( + frozen=True, + ) + 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..2e60bb1 100644 --- a/src/ucp_sdk/models/schemas/shopping/order.py +++ b/src/ucp_sdk/models/schemas/shopping/order.py @@ -19,76 +19,83 @@ from __future__ import annotations from pydantic import AnyUrl, BaseModel, ConfigDict + +from .. import ucp as ucp_1 from .types import ( - adjustment, - expectation, - fulfillment_event, - order_line_item, - total_resp, + adjustment, + expectation, + fulfillment_event, + order_line_item, + total, ) -from ..._internal import ResponseOrder -class PlatformConfig(BaseModel): - """Platform's order capability configuration.""" +class PlatformSchema(BaseModel): + """ + Platform's order capability configuration. + """ - model_config = ConfigDict( - extra="allow", - ) - webhook_url: AnyUrl - """ + 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.UcpMetadata + 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_data.py b/src/ucp_sdk/models/schemas/shopping/payment.py similarity index 61% rename from src/ucp_sdk/models/schemas/shopping/payment_data.py rename to src/ucp_sdk/models/schemas/shopping/payment.py index 5a8a03d..ba698c7 100644 --- a/src/ucp_sdk/models/schemas/shopping/payment_data.py +++ b/src/ucp_sdk/models/schemas/shopping/payment.py @@ -19,13 +19,21 @@ 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.""" +class Payment(BaseModel): + """ + Payment configuration containing handlers. + """ - model_config = ConfigDict( - extra="allow", - ) - payment_data: payment_instrument.PaymentInstrument + 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. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/payment_complete_request.py b/src/ucp_sdk/models/schemas/shopping/payment_complete_request.py new file mode 100644 index 0000000..0c9415b --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/payment_complete_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 + +from .types import payment_instrument + + +class PaymentCompleteRequest(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. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/payment_create_req.py b/src/ucp_sdk/models/schemas/shopping/payment_create_request.py similarity index 71% rename from src/ucp_sdk/models/schemas/shopping/payment_create_req.py rename to src/ucp_sdk/models/schemas/shopping/payment_create_request.py index 1b5359d..4bd95d6 100644 --- a/src/ucp_sdk/models/schemas/shopping/payment_create_req.py +++ b/src/ucp_sdk/models/schemas/shopping/payment_create_request.py @@ -19,20 +19,21 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict + from .types import payment_instrument class PaymentCreateRequest(BaseModel): - """Payment configuration containing handlers.""" + """ + 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_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/payment_update_req.py b/src/ucp_sdk/models/schemas/shopping/payment_update_request.py similarity index 71% rename from src/ucp_sdk/models/schemas/shopping/payment_update_req.py rename to src/ucp_sdk/models/schemas/shopping/payment_update_request.py index 083d0a7..a63bbfa 100644 --- a/src/ucp_sdk/models/schemas/shopping/payment_update_req.py +++ b/src/ucp_sdk/models/schemas/shopping/payment_update_request.py @@ -19,20 +19,21 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict + from .types import payment_instrument class PaymentUpdateRequest(BaseModel): - """Payment configuration containing handlers.""" + """ + 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/types/__init__.py b/src/ucp_sdk/models/schemas/shopping/types/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/__init__.py +++ b/src/ucp_sdk/models/schemas/shopping/types/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/shopping/types/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..53e3bd5 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/business_fulfillment_config.py @@ -0,0 +1,61 @@ +# 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/buyer_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/buyer_create_request.py new file mode 100644 index 0000000..0614375 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/buyer_create_request.py @@ -0,0 +1,43 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class BuyerCreateRequest(BaseModel): + 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. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/buyer_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/buyer_update_request.py new file mode 100644 index 0000000..5aea45e --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/buyer_update_request.py @@ -0,0 +1,43 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class BuyerUpdateRequest(BaseModel): + 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. + """ 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..d501c00 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/context.py @@ -0,0 +1,43 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class 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 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. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/context_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/context_create_request.py new file mode 100644 index 0000000..64035b4 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/context_create_request.py @@ -0,0 +1,43 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class ContextCreateRequest(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 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. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/context_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/context_update_request.py new file mode 100644 index 0000000..77ee792 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/context_update_request.py @@ -0,0 +1,43 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class ContextUpdateRequest(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 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. + """ 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..8ff2c27 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,25 @@ 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_create_request.py similarity index 78% rename from src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_req.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_create_request.py index 6bb4ef5..54854b7 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_create_request.py @@ -21,9 +21,11 @@ from pydantic import BaseModel, ConfigDict -class FulfillmentAvailableMethodRequest(BaseModel): - """Inventory availability hint for a fulfillment method type.""" +class FulfillmentAvailableMethodCreateRequest(BaseModel): + """ + Inventory availability hint for a fulfillment method type. + """ - model_config = ConfigDict( - extra="allow", - ) + model_config = ConfigDict( + extra="allow", + ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_req.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_update_request.py similarity index 78% rename from src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_req.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_update_request.py index d33445e..678c052 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_available_method_update_request.py @@ -21,9 +21,11 @@ from pydantic import BaseModel, ConfigDict -class FulfillmentOptionRequest(BaseModel): - """A fulfillment option within a group (e.g., Standard Shipping $5, Express $15).""" +class FulfillmentAvailableMethodUpdateRequest(BaseModel): + """ + Inventory availability hint for a fulfillment method type. + """ - model_config = ConfigDict( - extra="allow", - ) + 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_create_request.py similarity index 68% rename from src/ucp_sdk/models/schemas/shopping/types/fulfillment_req.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_create_request.py index 560ce75..32accc9 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_create_request.py @@ -19,18 +19,22 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict -from . import fulfillment_method_create_req +from . import fulfillment_method_create_request -class FulfillmentRequest(BaseModel): - """Container for fulfillment methods and availability.""" - model_config = ConfigDict( - extra="allow", - ) - methods: ( - list[fulfillment_method_create_req.FulfillmentMethodCreateRequest] | None - ) = None - """ +class FulfillmentCreateRequest(BaseModel): + """ + Container for fulfillment methods and availability. + """ + + model_config = ConfigDict( + extra="allow", + ) + 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_destination_resp.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination.py similarity index 61% rename from src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_resp.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination.py index 91ae5fb..f6232ff 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_resp.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination.py @@ -18,20 +18,24 @@ from __future__ import annotations -from pydantic import Field, RootModel -from . import retail_location_resp, shipping_destination_resp +from pydantic import ConfigDict, Field, RootModel +from . import retail_location, shipping_destination -class FulfillmentDestinationResponse( - RootModel[ - shipping_destination_resp.ShippingDestinationResponse - | retail_location_resp.RetailLocationResponse - ] + +class FulfillmentDestination( + RootModel[ + shipping_destination.ShippingDestination + | retail_location.RetailLocation + ] ): - root: ( - shipping_destination_resp.ShippingDestinationResponse - | retail_location_resp.RetailLocationResponse - ) = Field(..., title="Fulfillment Destination Response") - """ + model_config = ConfigDict( + frozen=True, + ) + 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_req.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_create_request.py similarity index 59% rename from src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_req.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_create_request.py index e8d45eb..bb6f986 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_create_request.py @@ -18,20 +18,24 @@ from __future__ import annotations -from pydantic import Field, RootModel -from . import retail_location_req, shipping_destination_req +from pydantic import ConfigDict, Field, RootModel +from . import retail_location, shipping_destination -class FulfillmentDestinationRequest( - RootModel[ - shipping_destination_req.ShippingDestinationRequest - | retail_location_req.RetailLocationRequest - ] + +class FulfillmentDestinationCreateRequest( + RootModel[ + shipping_destination.ShippingDestination + | retail_location.RetailLocation + ] ): - root: ( - shipping_destination_req.ShippingDestinationRequest - | retail_location_req.RetailLocationRequest - ) = Field(..., title="Fulfillment Destination Request") - """ + model_config = ConfigDict( + frozen=True, + ) + root: ( + shipping_destination.ShippingDestination + | retail_location.RetailLocation + ) = Field(..., title="Fulfillment Destination Create Request") + """ A destination for fulfillment. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_update_request.py new file mode 100644 index 0000000..4bcc34c --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_destination_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 ConfigDict, Field, RootModel + +from . import retail_location, shipping_destination + + +class FulfillmentDestinationUpdateRequest( + RootModel[ + shipping_destination.ShippingDestination + | retail_location.RetailLocation + ] +): + model_config = ConfigDict( + frozen=True, + ) + root: ( + shipping_destination.ShippingDestination + | retail_location.RetailLocation + ) = Field(..., title="Fulfillment Destination Update Request") + """ + 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_request.py similarity index 80% rename from src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_create_req.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_create_request.py index 171cf2b..99489e4 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_create_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_create_request.py @@ -22,12 +22,14 @@ class FulfillmentGroupCreateRequest(BaseModel): - """A merchant-generated package/group of line items with fulfillment options.""" + """ + A merchant-generated package/group of line items with fulfillment options. + """ - model_config = ConfigDict( - extra="allow", - ) - selected_option_id: str | None = None - """ + 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_req.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_update_request.py similarity index 80% rename from src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_update_req.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_update_request.py index 6b6893b..c830c71 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_update_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_group_update_request.py @@ -22,16 +22,18 @@ class FulfillmentGroupUpdateRequest(BaseModel): - """A merchant-generated package/group of line items with fulfillment options.""" + """ + A merchant-generated package/group of line items with fulfillment options. + """ - model_config = ConfigDict( - extra="allow", - ) - id: str - """ + model_config = ConfigDict( + extra="allow", + ) + id: str + """ Group identifier for referencing merchant-generated groups in updates. """ - 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_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..e022d8d 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,43 @@ 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_request.py similarity index 62% rename from src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_create_req.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_create_request.py index 3c2e61d..d5aacbb 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_create_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_create_request.py @@ -19,37 +19,48 @@ from __future__ import annotations from typing import Literal + from pydantic import BaseModel, ConfigDict -from . import fulfillment_destination_req, fulfillment_group_create_req + +from . import ( + fulfillment_destination_create_request, + fulfillment_group_create_request, +) class FulfillmentMethodCreateRequest(BaseModel): - """A fulfillment method (shipping or pickup) with destinations and groups.""" + """ + A fulfillment method (shipping or pickup) with destinations and groups. + """ - model_config = ConfigDict( - extra="allow", - ) - type: Literal["shipping", "pickup"] - """ + model_config = ConfigDict( + extra="allow", + ) + type: Literal["shipping", "pickup"] + """ Fulfillment method type. """ - line_item_ids: list[str] | None = None - """ + line_item_ids: list[str] + """ Line item IDs fulfilled via this method. """ - destinations: ( - list[fulfillment_destination_req.FulfillmentDestinationRequest] | None - ) = None - """ + destinations: ( + list[ + fulfillment_destination_create_request.FulfillmentDestinationCreateRequest + ] + | 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_create_req.FulfillmentGroupCreateRequest] | 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_req.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_request.py similarity index 63% rename from src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_req.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_request.py index 253f03f..e86694b 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_request.py @@ -19,36 +19,46 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict -from . import fulfillment_destination_req, fulfillment_group_update_req + +from . import ( + fulfillment_destination_update_request, + fulfillment_group_update_request, +) class FulfillmentMethodUpdateRequest(BaseModel): - """A fulfillment method (shipping or pickup) with destinations and groups.""" + """ + A fulfillment method (shipping or pickup) with destinations and groups. + """ - model_config = ConfigDict( - extra="allow", - ) - id: str - """ + model_config = ConfigDict( + extra="allow", + ) + id: str + """ Unique fulfillment method identifier. """ - line_item_ids: list[str] - """ + line_item_ids: list[str] + """ Line item IDs fulfilled via this method. """ - destinations: ( - list[fulfillment_destination_req.FulfillmentDestinationRequest] | None - ) = None - """ + destinations: ( + list[ + fulfillment_destination_update_request.FulfillmentDestinationUpdateRequest + ] + | 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_update_req.FulfillmentGroupUpdateRequest] | 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_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/payment_handler_create_req.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_create_request.py similarity index 77% rename from src/ucp_sdk/models/schemas/shopping/types/payment_handler_create_req.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_create_request.py index e8526ef..d08ee75 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/payment_handler_create_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_create_request.py @@ -21,7 +21,11 @@ from pydantic import BaseModel, ConfigDict -class PaymentHandlerCreateRequest(BaseModel): - model_config = ConfigDict( - extra="allow", - ) +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/payment_handler_update_req.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_update_request.py similarity index 77% rename from src/ucp_sdk/models/schemas/shopping/types/payment_handler_update_req.py rename to src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_update_request.py index ce959e0..363e7f3 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/payment_handler_update_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_option_update_request.py @@ -21,7 +21,11 @@ from pydantic import BaseModel, ConfigDict -class PaymentHandlerUpdateRequest(BaseModel): - model_config = ConfigDict( - extra="allow", - ) +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..849b10a --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_update_request.py @@ -0,0 +1,40 @@ +# 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_update_request + + +class FulfillmentUpdateRequest(BaseModel): + """ + Container for fulfillment methods and availability. + """ + + model_config = ConfigDict( + extra="allow", + ) + methods: ( + list[fulfillment_method_update_request.FulfillmentMethodUpdateRequest] + | 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 82% 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..e2b1954 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 - """ +class Item(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 - """ + 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_request.py similarity index 92% rename from src/ucp_sdk/models/schemas/shopping/types/item_create_req.py rename to src/ucp_sdk/models/schemas/shopping/types/item_create_request.py index 19e0d07..fd3f850 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/item_create_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/item_create_request.py @@ -22,10 +22,10 @@ class ItemCreateRequest(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - id: str - """ + 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_request.py similarity index 92% rename from src/ucp_sdk/models/schemas/shopping/types/item_update_req.py rename to src/ucp_sdk/models/schemas/shopping/types/item_update_request.py index 156fe97..735b984 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/item_update_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/item_update_request.py @@ -22,10 +22,10 @@ class ItemUpdateRequest(BaseModel): - model_config = ConfigDict( - extra="allow", - ) - id: str - """ + 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_request.py similarity index 75% rename from src/ucp_sdk/models/schemas/shopping/types/line_item_create_req.py rename to src/ucp_sdk/models/schemas/shopping/types/line_item_create_request.py index 310c501..754e4fe 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/line_item_create_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/line_item_create_request.py @@ -19,17 +19,20 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field -from . import item_create_req + +from . import item_create_request class LineItemCreateRequest(BaseModel): - """Line item object. Expected to use the currency of the parent object.""" + """ + 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) - """ + model_config = ConfigDict( + extra="allow", + ) + 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_req.py b/src/ucp_sdk/models/schemas/shopping/types/line_item_update_request.py similarity index 73% rename from src/ucp_sdk/models/schemas/shopping/types/line_item_update_req.py rename to src/ucp_sdk/models/schemas/shopping/types/line_item_update_request.py index 5322178..0470cb1 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/line_item_update_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/line_item_update_request.py @@ -19,22 +19,25 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field -from . import item_update_req + +from . import item_update_request class LineItemUpdateRequest(BaseModel): - """Line item object. Expected to use the currency of the parent object.""" + """ + 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) - """ + model_config = ConfigDict( + extra="allow", + ) + id: str + item: item_update_request.ItemUpdateRequest + quantity: int = Field(..., ge=1) + """ Quantity of the item being purchased. """ - 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/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..b908e47 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,44 @@ 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..051eef2 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message.py +++ b/src/ucp_sdk/models/schemas/shopping/types/message.py @@ -18,22 +18,26 @@ from __future__ import annotations -from pydantic import Field, RootModel +from pydantic import ConfigDict, Field, RootModel + from . import message_error, message_info, message_warning class Message( - RootModel[ - message_error.MessageError - | message_warning.MessageWarning - | message_info.MessageInfo - ] + RootModel[ + message_error.MessageError + | message_warning.MessageWarning + | message_info.MessageInfo + ] ): - root: ( - message_error.MessageError - | message_warning.MessageWarning - | message_info.MessageInfo - ) = Field(..., title="Message") - """ + model_config = ConfigDict( + frozen=True, + ) + 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..d1e6adb 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,37 @@ 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_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_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..4b7bf78 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,53 @@ 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 + +from . import payment_credential, postal_address + + +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. + """ + + +class SelectedPaymentInstrument(PaymentInstrument): + """ + A payment instrument with selection state. + """ + + model_config = ConfigDict( + extra="allow", + ) + selected: bool | None = None + """ + Whether this instrument is selected by the user. """ 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/postal_address_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/postal_address_create_request.py new file mode 100644 index 0000000..cdf804f --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/postal_address_create_request.py @@ -0,0 +1,63 @@ +# 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 PostalAddressCreateRequest(BaseModel): + 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 + """ + The street address. + """ + 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 + """ + 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 + """ + 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 + """ + The postal code. For example, 94043. + """ + first_name: str | None = None + """ + Optional. First name of the contact associated with the address. + """ + last_name: str | None = None + """ + Optional. Last name of the contact associated with the address. + """ + 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/postal_address_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/postal_address_update_request.py new file mode 100644 index 0000000..35b3f6f --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/postal_address_update_request.py @@ -0,0 +1,63 @@ +# 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 PostalAddressUpdateRequest(BaseModel): + 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 + """ + The street address. + """ + 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 + """ + 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 + """ + 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 + """ + The postal code. For example, 94043. + """ + first_name: str | None = None + """ + Optional. First name of the contact associated with the address. + """ + last_name: str | None = None + """ + Optional. Last name of the contact associated with the address. + """ + 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/retail_location_req.py b/src/ucp_sdk/models/schemas/shopping/types/retail_location_create_request.py similarity index 70% rename from src/ucp_sdk/models/schemas/shopping/types/retail_location_req.py rename to src/ucp_sdk/models/schemas/shopping/types/retail_location_create_request.py index 6b1a8e2..4d42caf 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/retail_location_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/retail_location_create_request.py @@ -19,20 +19,25 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict -from . import postal_address +from . import postal_address_create_request -class RetailLocationRequest(BaseModel): - """A pickup location (retail store, locker, etc.).""" - model_config = ConfigDict( - extra="allow", - ) - name: str - """ +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 - """ + address: postal_address_create_request.PostalAddressCreateRequest | 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..b5aa0de --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/retail_location_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 + +from . import postal_address_update_request + + +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_update_request.PostalAddressUpdateRequest | 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_create_request.py similarity index 82% rename from src/ucp_sdk/models/schemas/shopping/types/shipping_destination_resp.py rename to src/ucp_sdk/models/schemas/shopping/types/shipping_destination_create_request.py index 6ba62ae..169dfa6 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_resp.py +++ b/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_create_request.py @@ -19,16 +19,19 @@ from __future__ import annotations from pydantic import ConfigDict + from .postal_address import PostalAddress -class ShippingDestinationResponse(PostalAddress): - """Shipping destination.""" +class ShippingDestinationCreateRequest(PostalAddress): + """ + Shipping destination. + """ - model_config = ConfigDict( - extra="allow", - ) - id: str - """ + 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_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_update_request.py new file mode 100644 index 0000000..7712dea --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/shipping_destination_update_request.py @@ -0,0 +1,37 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict + +from .postal_address import PostalAddress + + +class ShippingDestinationUpdateRequest(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 75% 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..623c2dd 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,31 @@ 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_request.py similarity index 92% rename from src/ucp_sdk/models/schemas/shopping/types/total_create_req.py rename to src/ucp_sdk/models/schemas/shopping/types/total_create_request.py index ab0c7d2..6318099 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/total_create_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/total_create_request.py @@ -22,6 +22,6 @@ class TotalCreateRequest(BaseModel): - model_config = ConfigDict( - extra="allow", - ) + 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_request.py similarity index 92% rename from src/ucp_sdk/models/schemas/shopping/types/total_update_req.py rename to src/ucp_sdk/models/schemas/shopping/types/total_update_request.py index ef0dfe3..e8ca037 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/total_update_req.py +++ b/src/ucp_sdk/models/schemas/shopping/types/total_update_request.py @@ -22,6 +22,6 @@ class TotalUpdateRequest(BaseModel): - model_config = ConfigDict( - extra="allow", - ) + model_config = ConfigDict( + extra="allow", + ) diff --git a/src/ucp_sdk/models/handlers/__init__.py b/src/ucp_sdk/models/schemas/transports/__init__.py similarity index 99% rename from src/ucp_sdk/models/handlers/__init__.py rename to src/ucp_sdk/models/schemas/transports/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/handlers/__init__.py +++ b/src/ucp_sdk/models/schemas/transports/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/transports/embedded_config.py b/src/ucp_sdk/models/schemas/transports/embedded_config.py new file mode 100644 index 0000000..8f3cba7 --- /dev/null +++ b/src/ucp_sdk/models/schemas/transports/embedded_config.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 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", + ) + delegate: list[str] | None = None + """ + 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..bd041f5 100644 --- a/src/ucp_sdk/models/schemas/ucp.py +++ b/src/ucp_sdk/models/schemas/ucp.py @@ -18,20 +18,178 @@ 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 Version(RootModel[str]): + model_config = ConfigDict( + frozen=True, + ) + root: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$") + """ + UCP version in YYYY-MM-DD format. + """ + + +class ReverseDomainName(RootModel[str]): + model_config = ConfigDict( + frozen=True, + ) + 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: Version + 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: Version + services: Any + capabilities: Any | None = None + payment_handlers: Any + + +class ResponseCheckoutSchema(BaseModel): + """ + UCP metadata for checkout responses. + """ + + model_config = ConfigDict( + extra="allow", + ) + version: Version + services: Any | None = None + capabilities: Any | None = None + payment_handlers: Any + + +class ResponseCartSchema(RootModel[Any]): + model_config = ConfigDict( + frozen=True, + ) + 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: Version + services: dict[ReverseDomainName, list[service.Base]] | None = None + """ + Service registry keyed by reverse-domain name. + """ + capabilities: Any | None = None + payment_handlers: ( + dict[ReverseDomainName, list[payment_handler.Base]] | None + ) = None + """ + Payment handler registry keyed by reverse-domain name. + """ + + +class UcpMetadata( + RootModel[ + PlatformSchema + | BusinessSchema + | ResponseCheckoutSchema + | ResponseOrderSchema + | ResponseCartSchema + ] +): + model_config = ConfigDict( + frozen=True, + ) + root: ( + PlatformSchema + | BusinessSchema + | ResponseCheckoutSchema + | ResponseOrderSchema + | ResponseCartSchema + ) = Field(..., title="UCP Metadata") + """ + Protocol metadata for discovery profiles and responses. Uses slim schema pattern with context-specific required fields. + """ diff --git a/src/ucp_sdk/models/services/__init__.py b/src/ucp_sdk/models/services/__init__.py deleted file mode 100644 index 1252d6b..0000000 --- a/src/ucp_sdk/models/services/__init__.py +++ /dev/null @@ -1,17 +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 diff --git a/src/ucp_sdk/models/services/service_schema.py b/src/ucp_sdk/models/services/service_schema.py deleted file mode 100644 index 3272570..0000000 --- a/src/ucp_sdk/models/services/service_schema.py +++ /dev/null @@ -1,23 +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 .._internal import A2a, Embedded, Mcp, Rest, UcpService - -__all__ = ["A2a", "Embedded", "Mcp", "Rest", "UcpService"] diff --git a/src/ucp_sdk/models/services/shopping/__init__.py b/src/ucp_sdk/models/services/shopping/__init__.py deleted file mode 100644 index 1252d6b..0000000 --- a/src/ucp_sdk/models/services/shopping/__init__.py +++ /dev/null @@ -1,17 +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 diff --git a/src/ucp_sdk/models/services/shopping/embedded_openrpc.py b/src/ucp_sdk/models/services/shopping/embedded_openrpc.py deleted file mode 100644 index e8b3b8c..0000000 --- a/src/ucp_sdk/models/services/shopping/embedded_openrpc.py +++ /dev/null @@ -1,26 +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 RootModel - - -class Model(RootModel[Any]): - root: Any diff --git a/src/ucp_sdk/models/services/shopping/mcp_openrpc.py b/src/ucp_sdk/models/services/shopping/mcp_openrpc.py deleted file mode 100644 index e8b3b8c..0000000 --- a/src/ucp_sdk/models/services/shopping/mcp_openrpc.py +++ /dev/null @@ -1,26 +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 RootModel - - -class Model(RootModel[Any]): - root: Any diff --git a/src/ucp_sdk/models/services/shopping/rest_openapi.py b/src/ucp_sdk/models/services/shopping/rest_openapi.py deleted file mode 100644 index e8b3b8c..0000000 --- a/src/ucp_sdk/models/services/shopping/rest_openapi.py +++ /dev/null @@ -1,26 +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 RootModel - - -class Model(RootModel[Any]): - root: Any diff --git a/src/ucp_sdk/py.typed b/src/ucp_sdk/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/templates/pydantic_v2/RootModel.jinja2 b/templates/pydantic_v2/RootModel.jinja2 new file mode 100644 index 0000000..fdc4a0b --- /dev/null +++ b/templates/pydantic_v2/RootModel.jinja2 @@ -0,0 +1,68 @@ +{%- macro get_type_hint(_fields, use_base_type) -%} +{%- if _fields -%} +{#There will only ever be a single field for RootModel#} +{%- if use_base_type -%} +{{- _fields[0].base_type_hint}} +{%- else -%} +{{- _fields[0].type_hint}} +{%- endif -%} +{%- endif -%} +{%- endmacro -%} + + +{% for decorator in decorators -%} +{{ decorator }} +{% endfor -%} + +{#- Use base_type_hint in generic when regex_engine is set to avoid evaluating pattern before config is processed -#} +{%- set use_base_type = config and config.regex_engine -%} +class {{ class_name }}({{ base_class }}{%- if fields -%}[{{get_type_hint(fields, use_base_type)}}]{%- endif -%}):{% if comment is defined %} # {{ comment }}{% endif %} +{%- if description %} + """ + {{ description | escape_docstring }} + """ +{%- endif %} +{%- if config %} +{%- filter indent(4) %} +model_config = ConfigDict( + frozen=True, +{%- for field_name, value in config.dict(exclude_unset=True).items() %} + {%- if field_name != 'frozen' %} + {{ field_name }}={{ value }}, + {%- endif %} +{%- endfor %} +) +{%- endfilter %} +{%- else %} + model_config = ConfigDict( + frozen=True, + ) +{%- endif %} +{%- for line in class_body_lines %} + {{ line }} +{%- endfor %} +{%- if not fields and not description and not config and not class_body_lines %} + pass +{%- else %} + {%- set field = fields[0] %} + {%- if not field.annotated and field.field %} + root: {{ field.type_hint }} = {{ field.field }} + {%- else %} + {%- if field.annotated %} + root: {{ field.annotated }} + {%- else %} + root: {{ field.type_hint }} + {%- endif %} + {%- if not field.has_default_factory_in_field and not (field.required or (field.represented_default == 'None' and field.strip_default_none)) + %} = {{ field.represented_default }} + {%- endif -%} + {%- endif %} + {%- if field.docstring %} + """ + {{ field.docstring | escape_docstring }} + """ + {%- elif field.inline_field_docstring %} + {{ field.inline_field_docstring }} + + {%- endif %} +{%- endif %}