Skip to content

Commit 99e5979

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 99e5979

209 files changed

Lines changed: 5043 additions & 736 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/conventional-commits.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ jobs:
3333
runs-on: ubuntu-latest
3434
steps:
3535
- name: semantic-pull-request
36-
uses: amannn/action-semantic-pull-request@v6
36+
# zizmor: ignore[unpinned-uses]
37+
uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
3738
env:
3839
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3940
with:

.github/workflows/linter.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,20 @@ on:
2020
pull_request:
2121
branches: [main]
2222

23+
permissions:
24+
contents: read
25+
2326
jobs:
2427
pre-commit:
2528
runs-on: ubuntu-latest
2629
steps:
27-
- uses: actions/checkout@v5
28-
- uses: actions/setup-python@v6
30+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
31+
with:
32+
persist-credentials: false
33+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
2934
with:
3035
python-version: '3.x'
31-
- uses: pre-commit/action@v3.0.1
36+
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
3237
env:
3338
# workaround for pre-commit/action not being updated to node24
3439
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

.github/workflows/tests.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ jobs:
3434
steps:
3535
- name: Checkout repository
3636
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
37+
with:
38+
persist-credentials: false
3739
- name: Set up Python
3840
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
3941
with:
@@ -49,14 +51,16 @@ jobs:
4951
steps:
5052
- name: Checkout repository
5153
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
54+
with:
55+
persist-credentials: false
5256
- name: Install uv
5357
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
5458
with:
5559
enable-cache: true
5660
- name: Regenerate models from the pinned spec version
57-
# SDK 0.4.x targets UCP 2026-04-08 (see the README compatibility
61+
# SDK 0.5.x targets UCP 2026-08-25 (see the README compatibility
5862
# table). Bump this pin together with the SDK version line.
59-
run: ./generate_models.sh 2026-04-08
63+
run: ./generate_models.sh 2026-08-25
6064
- name: Normalize file endings (as pre-commit's end-of-file-fixer does)
6165
run: |
6266
python3 - <<'PY'
@@ -72,7 +76,7 @@ jobs:
7276
run: |
7377
git add -A -- src/ucp_sdk/models/schemas
7478
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."
79+
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."
7680
git --no-pager diff --cached --stat -- src/ucp_sdk/models/schemas
7781
git --no-pager diff --cached -- src/ucp_sdk/models/schemas
7882
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: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ UCP schema:
4242

4343
| SDK Version | UCP Schema Version |
4444
| ----------------- | ------------------ |
45-
| **`0.4.x`** | **`2026-04-08`** |
45+
| **`0.5.x`** | **`2026-08-25`** |
46+
| `0.4.x` | `2026-04-08` |
4647
| `0.3.x` | `2026-01-23` |
4748
| `0.2.x` / `0.1.x` | `2026-01-11` |
4849

@@ -71,20 +72,22 @@ from ucp_sdk.models.schemas.shopping.checkout import Checkout
7172
checkout = Checkout.model_validate(checkout_data)
7273

7374
# Access typed fields
74-
print(checkout.status) # "incomplete" | "ready_for_complete" | ...
75-
print(checkout.currency) # ISO 4217 currency code
75+
print(checkout.status) # "incomplete" | "ready_for_complete" | ...
76+
print(checkout.currency) # ISO 4217 currency code
7677
for item in checkout.line_items:
7778
print(f"{item.item.title}: {item.quantity}")
7879
```
7980

8081
### Available model packages
8182

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 |
83+
| Package | Description |
84+
| --------------------------------------- | ----------------------------------------------------------------- |
85+
| `ucp_sdk.models.schemas.common` | Location search/lookup, identity linking, loyalty, payment terms |
86+
| `ucp_sdk.models.schemas.common.types` | Shared primitives (amounts, prices, totals, postal address, etc.) |
87+
| `ucp_sdk.models.schemas.shopping` | Checkout, cart, catalog, order, buyer consent, permalink models |
88+
| `ucp_sdk.models.schemas.shopping.types` | Line items, adjustments, fulfillments, destinations, attribution |
89+
| `ucp_sdk.models.schemas.transports` | REST, MCP, and embedded protocol bindings |
90+
| `ucp_sdk.models.schemas` | Service definitions, capabilities, profile, payment handlers |
8891

8992
### Validation
9093

@@ -133,7 +136,7 @@ uv sync
133136
```
134137

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

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

generate_models.sh

Lines changed: 12 additions & 8 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"
@@ -46,6 +39,7 @@ fi
4639
# Ensure ucp directory is clean before cloning
4740
rm -rf ucp
4841
git clone -b "$BRANCH" --depth 1 https://github.com/Universal-Commerce-Protocol/ucp ucp
42+
rm -rf ucp/.git
4943

5044
# Output directory
5145
OUTPUT_DIR="src/ucp_sdk/models/schemas"
@@ -101,9 +95,19 @@ uv run \
10195
echo "Post-processing generated models (constraints the generator ignores)..."
10296
uv run python postprocess_models.py || exit 1
10397

98+
# Normalize file endings (as pre-commit's end-of-file-fixer does)
99+
python3 - <<'PY'
100+
from pathlib import Path
101+
102+
for path in Path("src/ucp_sdk/models/schemas").rglob("*.py"):
103+
text = path.read_text(encoding="utf-8")
104+
fixed = text.rstrip("\n") + "\n" if text.strip() else ""
105+
if fixed != text:
106+
path.write_text(fixed, encoding="utf-8")
107+
PY
108+
104109
echo "Formatting generated models..."
105110
uv run ruff format
106111
uv run ruff check --fix "$OUTPUT_DIR"
107112

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

postprocess_models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ def find_root_min_properties(schema_dir):
234234
"cannot map to a class\n"
235235
)
236236
continue
237-
found[title] = minimum
237+
found[_alias_name(title)] = minimum
238238
return found
239239

240240

preprocess_schemas.py

Lines changed: 16 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 = []
@@ -291,6 +295,10 @@ def preprocess_full_schema(schema, entity_def=None):
291295
Main entry point for normalizing a single schema file.
292296
Uses bottom-up iteration to ensure nested structures are flat before parents process them.
293297
"""
298+
# Remove $id so datamodel-code-generator resolves relative $refs strictly
299+
# via the local filesystem rather than attempting remote HTTP fetching.
300+
schema.pop("$id", None)
301+
294302
# 1. Discovery: find all dictionaries in the tree
295303
nodes = [n for n in iter_nodes(schema) if isinstance(n, dict)]
296304

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"]

0 commit comments

Comments
 (0)