Skip to content

Commit b74d02c

Browse files
committed
feat(models): update Python SDK schemas for UCP release 2026-08-25
- Update schema preprocessing pipeline for v2026-08-25 response definitions - Regenerate Pydantic models from UCP release/2026-08-25 JSON schemas - Restructure models into common, shopping, and transport verticals - Add Location Search/Lookup, Loyalty, Request Constraints, Permalinks, and Actions models - Move shared primitives and payment extensions to common namespace - Bump package version to 0.5.0 in pyproject.toml and update README.md
1 parent 51bf73c commit b74d02c

230 files changed

Lines changed: 6132 additions & 2261 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.codespellignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ mor
22
tok
33
fpan
44
dpan
5+
requestor

.github/workflows/governance.yml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,21 @@ on:
1313
assigned,
1414
unassigned
1515
]
16-
workflow_run:
17-
workflows: ["PR Review Listener"]
18-
types: [completed]
16+
pull_request_review:
17+
types: [submitted, dismissed]
1918

2019
jobs:
2120
governance:
2221
name: Reviews
22+
# skip for draft PRs
23+
if: github.event.pull_request.draft == false
2324
permissions:
2425
statuses: write
2526
contents: read
2627
pull-requests: read
2728
# Use the reusable workflow defined in the central governance repository
2829
# zizmor: ignore[ref-confusion]
2930
uses: Universal-Commerce-Protocol/.github/.github/workflows/reusable-governance.yml@main
30-
with:
31-
pr-number: ${{ github.event.pull_request.number }}
32-
commit-sha: ${{ github.event.pull_request.head.sha || github.event.workflow_run.head_sha }}
3331
secrets:
3432
# Required: An org-level Read token to read team memberships
3533
ORG_READ_TOKEN: ${{ secrets.ORG_READ_TOKEN }}

.github/workflows/tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ jobs:
5454
with:
5555
enable-cache: true
5656
- name: Regenerate models from the pinned spec version
57-
# SDK 0.4.x targets UCP 2026-04-08 (see the README compatibility
57+
# SDK 0.5.x targets UCP 2026-08-25 (see the README compatibility
5858
# table). Bump this pin together with the SDK version line.
59-
run: ./generate_models.sh 2026-04-08
59+
run: ./generate_models.sh 2026-08-25
6060
- name: Normalize file endings (as pre-commit's end-of-file-fixer does)
6161
run: |
6262
python3 - <<'PY'
@@ -72,7 +72,7 @@ jobs:
7272
run: |
7373
git add -A -- src/ucp_sdk/models/schemas
7474
if ! git diff --cached --quiet -- src/ucp_sdk/models/schemas; then
75-
echo "::error::Committed models differ from regeneration against the pinned UCP spec. Either the generation pipeline is broken, or the models were edited without regenerating. Run ./generate_models.sh 2026-04-08 and commit the result."
75+
echo "::error::Committed models differ from regeneration against the pinned UCP spec. Either the generation pipeline is broken, or the models were edited without regenerating. Run ./generate_models.sh 2026-08-25 and commit the result."
7676
git --no-pager diff --cached --stat -- src/ucp_sdk/models/schemas
7777
git --no-pager diff --cached -- src/ucp_sdk/models/schemas
7878
exit 1

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ node_modules/
33
__pycache__/
44
.venv/
55
*.py[cod]
6+
ucp/
7+
uv.lock

README.md

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,6 @@ This repository contains the Python SDK for the
3535
models for UCP schemas, making it easy to build UCP-compliant applications in
3636
Python.
3737

38-
### UCP Version Compatibility
39-
40-
Each version of the Python SDK is generated against a specific version of the
41-
UCP schema:
42-
43-
| SDK Version | UCP Schema Version |
44-
| ----------------- | ------------------ |
45-
| **`0.4.x`** | **`2026-04-08`** |
46-
| `0.3.x` | `2026-01-23` |
47-
| `0.2.x` / `0.1.x` | `2026-01-11` |
48-
4938
## Installation
5039

5140
To use this SDK in your own project, install it from PyPI:
@@ -71,20 +60,22 @@ from ucp_sdk.models.schemas.shopping.checkout import Checkout
7160
checkout = Checkout.model_validate(checkout_data)
7261

7362
# Access typed fields
74-
print(checkout.status) # "incomplete" | "ready_for_complete" | ...
75-
print(checkout.currency) # ISO 4217 currency code
63+
print(checkout.status) # "incomplete" | "ready_for_complete" | ...
64+
print(checkout.currency) # ISO 4217 currency code
7665
for item in checkout.line_items:
7766
print(f"{item.item.title}: {item.quantity}")
7867
```
7968

8069
### Available model packages
8170

82-
| Package | Description |
83-
| --------------------------------------- | --------------------------------------------------- |
84-
| `ucp_sdk.models.schemas.shopping` | Checkout, cart, catalog, order, payment models |
85-
| `ucp_sdk.models.schemas.shopping.types` | Line items, totals, buyer, fulfillment, signals |
86-
| `ucp_sdk.models.schemas.transports` | REST, MCP, and embedded protocol bindings |
87-
| `ucp_sdk.models.schemas` | Service definitions, capabilities, payment handlers |
71+
| Package | Description |
72+
| --------------------------------------- | ----------------------------------------------------------------- |
73+
| `ucp_sdk.models.schemas.common` | Location search/lookup, identity linking, loyalty, payment terms |
74+
| `ucp_sdk.models.schemas.common.types` | Shared primitives (amounts, prices, totals, postal address, etc.) |
75+
| `ucp_sdk.models.schemas.shopping` | Checkout, cart, catalog, order, buyer consent, permalink models |
76+
| `ucp_sdk.models.schemas.shopping.types` | Line items, adjustments, fulfillments, destinations, attribution |
77+
| `ucp_sdk.models.schemas.transports` | REST, MCP, and embedded protocol bindings |
78+
| `ucp_sdk.models.schemas` | Service definitions, capabilities, profile, payment handlers |
8879

8980
### Validation
9081

@@ -133,21 +124,13 @@ uv sync
133124
```
134125

135126
Where `<version>` is the version of the UCP specification to use (for example,
136-
"2026-04-08").
127+
"2026-08-25").
137128

138129
If no version is specified, the `main` branch of the
139130
[UCP repo](https://github.com/Universal-Commerce-Protocol/ucp) will be used.
140131

141132
The generated code is automatically formatted using `ruff`.
142133

143-
### Running Tests
144-
145-
Run the schema preprocessing test suite with:
146-
147-
```bash
148-
uv run python -m unittest discover -s tests -p "test_*.py"
149-
```
150-
151134
## Contributing
152135

153136
We welcome community contributions. See our

generate_models.sh

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,6 @@ if ! command -v git &> /dev/null; then
2727
exit 1
2828
fi
2929

30-
# Check if uv is installed before cloning or modifying generated files
31-
if ! command -v uv &> /dev/null; then
32-
echo "Error: uv not found."
33-
echo "Please install uv: curl -LsSf https://astral.sh/uv/install.sh | sh"
34-
exit 1
35-
fi
36-
3730
# UCP Version to use (if provided, use release/$1 branch; otherwise, use main)
3831
if [ -z "$1" ]; then
3932
BRANCH="main"
@@ -53,20 +46,18 @@ OUTPUT_DIR="src/ucp_sdk/models/schemas"
5346
# Schema directory (relative to this script)
5447
SCHEMA_DIR="ucp/source/schemas"
5548

56-
# Snapshot the pristine schemas before preprocessing. postprocess_models.py
57-
# reads array contains/minContains/maxContains from these originals because
58-
# preprocessing merges allOf branches and a JSON node holds only one contains,
59-
# so a second contains keyword (e.g. "exactly one total") would otherwise be
60-
# silently dropped before the post-processor could see it.
61-
RAW_SCHEMA_DIR="ucp/raw_schemas"
62-
rm -rf "$RAW_SCHEMA_DIR"
63-
cp -R "$SCHEMA_DIR" "$RAW_SCHEMA_DIR"
64-
6549
echo "Preprocessing schemas..."
6650
uv run python preprocess_schemas.py
6751

6852
echo "Generating Pydantic models from preprocessed schemas..."
6953

54+
# Check if uv is installed
55+
if ! command -v uv &> /dev/null; then
56+
echo "Error: uv not found."
57+
echo "Please install uv: curl -LsSf https://astral.sh/uv/install.sh | sh"
58+
exit 1
59+
fi
60+
7061
# Ensure output directory is clean
7162
rm -r -f "$OUTPUT_DIR"
7263
mkdir -p "$OUTPUT_DIR"
@@ -75,7 +66,6 @@ mkdir -p "$OUTPUT_DIR"
7566
# Run generation using uv
7667
# We use --use-schema-description to use descriptions from JSON schema as docstrings
7768
# We use --field-constraints to include validation constraints (regex, min/max, etc.)
78-
# We use --reuse-model to collapse structurally identical generated types.
7969
# Note: Formatting is done as a post-processing step.
8070
uv run \
8171
--link-mode=copy \
@@ -91,19 +81,25 @@ uv run \
9181
--enum-field-as-literal all \
9282
--disable-timestamp \
9383
--use-double-quotes \
94-
--extra-fields=allow \
95-
--use-type-alias \
96-
--reuse-model \
84+
--no-use-annotated \
85+
--allow-extra-fields \
9786
--custom-template-dir templates \
9887
--additional-imports pydantic.ConfigDict
9988

10089

101-
echo "Post-processing generated models (constraints the generator ignores)..."
102-
uv run python postprocess_models.py || exit 1
90+
# Normalize file endings (as pre-commit's end-of-file-fixer does)
91+
python3 - <<'PY'
92+
from pathlib import Path
93+
94+
for path in Path("src/ucp_sdk/models/schemas").rglob("*.py"):
95+
text = path.read_text(encoding="utf-8")
96+
fixed = text.rstrip("\n") + "\n" if text.strip() else ""
97+
if fixed != text:
98+
path.write_text(fixed, encoding="utf-8")
99+
PY
103100

104101
echo "Formatting generated models..."
105102
uv run ruff format
106103
uv run ruff check --fix "$OUTPUT_DIR"
107104

108-
109105
echo "Done. Models generated in $OUTPUT_DIR"

preprocess_schemas.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,15 @@ def _process_all_of_item(item, node, root, state):
141141

142142
# Extract polymorphic branches (anyOf, oneOf) to keep the node flat
143143
for poly_key in ["anyOf", "oneOf"]:
144-
if poly_key in item:
144+
if poly_key in item and isinstance(item[poly_key], list):
145145
state["poly_branches"].setdefault(poly_key, []).extend(
146146
item.pop(poly_key)
147147
)
148148

149149
# Merge core property definitions and requirements
150-
if "properties" in item:
150+
if "properties" in item and isinstance(item["properties"], dict):
151151
state["merged_properties"].update(item["properties"])
152-
if "required" in item:
152+
if "required" in item and isinstance(item["required"], list):
153153
for req in item["required"]:
154154
if req not in state["merged_required"]:
155155
state["merged_required"].append(req)
@@ -196,7 +196,7 @@ def merge_all_of_to_node(node, root):
196196
models if inheritance is flattened at the schema level rather than relying on
197197
complex 'allOf' chains which can lead to redundant intermediate classes.
198198
"""
199-
if "allOf" not in node:
199+
if "allOf" not in node or not isinstance(node["allOf"], list):
200200
return
201201

202202
all_of_sources = node.pop("allOf")
@@ -221,15 +221,19 @@ def distribute_properties_to_branches(node):
221221
model in Pydantic. Without this, a generated union model might miss required
222222
common fields if it's treated as a pure 'oneOf' alternative.
223223
"""
224-
if "properties" not in node:
224+
if "properties" not in node or not isinstance(node["properties"], dict):
225225
return
226226

227227
base_props = node["properties"]
228-
base_req = node.get("required", [])
228+
base_req = (
229+
node.get("required", [])
230+
if isinstance(node.get("required"), list)
231+
else []
232+
)
229233
base_type = node.get("type")
230234

231235
for poly_key in ["anyOf", "oneOf"]:
232-
if poly_key not in node:
236+
if poly_key not in node or not isinstance(node[poly_key], list):
233237
continue
234238

235239
updated_branches = []
@@ -263,7 +267,7 @@ def flatten_entity_reference(node, entity_definition):
263267
Replaces $ref to 'ucp.json#/$defs/entity' with actual logic.
264268
This effectively converts 'Entity' inheritance into direct 'BaseModel' fields.
265269
"""
266-
if "allOf" not in node:
270+
if "allOf" not in node or not isinstance(node["allOf"], list):
267271
return
268272

269273
filtered_all_of = []

pyproject.toml

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
[project]
22
name = "ucp-sdk"
3-
version = "0.4.6"
3+
version = "0.5.0"
44
description = "UCP Python SDK"
55
readme = "README.md"
66
license = {file = "LICENSE"}
77
authors = [
8+
{ name = "Enric Cusell", email = "cusell@google.com" },
89
{ name = "Federico D'Amato", email = "damaz@google.com" }
910
]
1011
classifiers = [
@@ -26,14 +27,7 @@ dependencies = [
2627

2728
[dependency-groups]
2829
dev = [
29-
# Exact pins: the model-drift CI job regenerates the models against the
30-
# pinned UCP spec and asserts byte-identical output, so the codegen
31-
# toolchain must resolve reproducibly. (The floating ">=0.50.0" bound
32-
# broke on 2026-08-03: datamodel-code-generator 0.72.0 mis-resolves the
33-
# spec's remote $refs and emits incomplete models.) Bump these pins and
34-
# regenerate the models in the same PR.
35-
"datamodel-code-generator[http]==0.71.0",
36-
"ruff==0.16.1",
30+
"datamodel-code-generator[http,ruff]>=0.50.0",
3731
]
3832

3933
[build-system]
@@ -75,7 +69,10 @@ skip-magic-trailing-comma = false
7569
line-ending = "auto"
7670

7771
[tool.ruff.lint.per-file-ignores]
78-
"src/ucp_sdk/models/schemas/**/*.py" = ["E501", "D", "N801"]
72+
"src/ucp_sdk/models/schemas/**/*.py" = ["E501", "D", "N801", "N815"]
73+
"preprocess_schemas.py" = ["E501", "D"]
74+
"postprocess_models.py" = ["E501", "D"]
75+
"tests/**/*.py" = ["E501", "D"]
7976

8077
[tool.ruff.lint]
8178
select = ["E", "F", "W", "B", "C4", "SIM", "N", "UP", "D", "PTH", "T20"]

src/ucp_sdk/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright 2026 UCP Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""UCP Python SDK."""
16+
17+
__version__ = "0.5.0"

src/ucp_sdk/models/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2026 UCP Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""UCP models."""

0 commit comments

Comments
 (0)