Skip to content

feat(generator): support arbitrary ucp refs and regenerate c5c6139 models#1

Draft
caohy1988 wants to merge 2 commits intomainfrom
regen-ucp-c5c6139
Draft

feat(generator): support arbitrary ucp refs and regenerate c5c6139 models#1
caohy1988 wants to merge 2 commits intomainfrom
regen-ucp-c5c6139

Conversation

@caohy1988
Copy link
Copy Markdown
Owner

@caohy1988 caohy1988 commented May 6, 2026

Regenerates the SDK against Universal-Commerce-Protocol/ucp@c5c61396ebf9a5afb9c4169bfa3af1409daa6fd6 — the identity-linking OAuth 2.0 foundation merge from PR #354 plus the v2026-04-08 features. Also fixes a long-standing preprocessor gap that was producing RootModel[Any] wrappers for any capability extension whose $defs lived under a reverse-domain key (identity_linking, fulfillment, …).

Two commits:

  • 3388931 — generator + preprocessor + tests + CI
  • 47fac9c — regenerated model tree

What changed since the previous push (CI/review feedback):

  • End-of-file normalization in the generator. Pre-commit's end-of-file-fixer was firing on every regen because codegen emits a trailing blank line on each generated module. The script now post-processes the entire tree under src/ucp_sdk/models/schemas and collapses the EOF to exactly one newline. Verified: a second consecutive ./generate_models.sh --ref c5c6139 produces a byte-identical __init__.py, and pre-commit run end-of-file-fixer --all-files reports Passed.

Earlier review-cycle changes still on this branch:

  • set -euo pipefail + git -C ucp checkout + git -C ucp rev-parse --verify HEAD. A bad ref aborts before the regen step rather than silently rebuilding from the default branch.
  • ruff check --fix --ignore "D,E501" matches .pre-commit-config.yaml so benign auto-generated docstring noise no longer aborts the script under set -e.
  • _schema_ref.py is post-formatted with uv run ruff format so the committed file matches whatever ruff would produce on subsequent runs. Long --ref values that ruff would otherwise wrap are pre-wrapped at write time.
  • lift_capability_extension_defs() lifts capability-extension $defs from dev.ucp.<…>/{platform,business}_schema to top-level {file_stem}_{platform,business}_schema and adds a root oneOf so codegen produces typed Unions. Iteration uses an explicit tuple ("platform_schema", "business_schema") rather than a set, so diffs are deterministic across Python hash seeds. Verified no external $ref points into the dotted-key path before removing it.
  • IdentityLinking exposes typed ScopePolicy, ScopeToken, Config, IdentityLinkingPlatformSchema, IdentityLinkingBusinessSchema, with Config.scopes: dict[ScopeToken, ScopePolicy]. The smoke test instantiates a real business-side payload and asserts the typed walk all the way down.
  • tests/test_generate_models.py invokes the generator with a nonexistent SHA in a temp tree and asserts nonzero exit plus that _schema_ref.py wasn't rewritten.

Reproducing locally:

./generate_models.sh --ref c5c61396ebf9a5afb9c4169bfa3af1409daa6fd6
./generate_models.sh --ref c5c61396ebf9a5afb9c4169bfa3af1409daa6fd6   # second run — no diff
uv run pytest -v                      # 11 passed
uv tool run pre-commit run --all-files
shellcheck generate_models.sh         # clean

Acceptance for issue #8: shopping/cart.py, shopping/catalog_{search,lookup}.py (with get_product_* defs), common/identity_linking.py (typed config.scopes / scope_policy), all four Context fields, Total.amount = SignedAmount (negatives accepted), open total / error / info / warning vocabularies, standalone info_code / warning_code, provenance constant.

@caohy1988 caohy1988 force-pushed the regen-ucp-c5c6139 branch from 17539f2 to 03fa011 Compare May 6, 2026 17:15
@caohy1988 caohy1988 changed the title [review-artifact] Regenerate models from ucp@c5c6139 (carries known blockers) Regenerate models from ucp@c5c6139 (review checkpoint, has known blockers) May 6, 2026
@caohy1988 caohy1988 force-pushed the regen-ucp-c5c6139 branch from 03fa011 to f6ead45 Compare May 6, 2026 17:47
@caohy1988 caohy1988 changed the title Regenerate models from ucp@c5c6139 (review checkpoint, has known blockers) feat(generator): support arbitrary ucp refs and regenerate c5c6139 models May 6, 2026
@caohy1988 caohy1988 force-pushed the regen-ucp-c5c6139 branch from f6ead45 to 459c29a Compare May 6, 2026 17:56
caohy1988 added 2 commits May 6, 2026 12:30
Three generator changes plus tests, all of which together let the SDK
regenerate from an arbitrary ucp commit and produce typed capability
extensions instead of `RootModel[Any]` wrappers.

generate_models.sh runs under `set -euo pipefail` now, so a failed
checkout (typo, network blip, missing ref) aborts the script before
the regen step rather than silently rebuilding from whatever default
branch happened to be cloned. The ref-mode path uses `git -C ucp
checkout` and an explicit `git -C ucp rev-parse --verify HEAD` to
make the failure point loud. The `ruff check --fix` post-step matches
pre-commit's ignore list (D, E501) so benign lint diffs in the
auto-generated tree no longer abort the whole regen under set -e.

Two regen-stability fixes so future runs don't dirty the working
tree on every invocation. First, `_schema_ref.py` is written and then
explicitly run through `uv run ruff format src/ucp_sdk/_schema_ref.py`
so the committed file matches whatever ruff would produce on its own
on subsequent regens. Without this, a long --ref or invocation
produces a line that ruff format would later wrap. Second, every
generated module under src/ucp_sdk/models/schemas is post-processed
to collapse the trailing blank line that codegen emits down to a
single newline; otherwise pre-commit's end-of-file-fixer fires on
every regen and adds noise to the diff.

preprocess_schemas.py grows lift_capability_extension_defs(). Capability
extension files (identity_linking.json, fulfillment.json, …) nest
their typed schemas under a reverse-domain $defs key
(`dev.ucp.common.identity_linking`, `dev.ucp.shopping.fulfillment`).
The dotted key is not a valid Python identifier, so datamodel-codegen
can't reach the inner platform_schema / business_schema and falls
back to RootModel[Any]. The new pass lifts those children to top-level
$defs as `{file_stem}_{platform,business}_schema`, drops the dotted
holder, and adds a root oneOf so codegen anchors on something walkable.
Iteration over the child names uses a fixed tuple rather than a set,
so generated diffs stay deterministic across Python hash seeds.
Capability-extension files have no external $ref into the dotted-key
path (verified across ucp/source/schemas), so removing it is safe.
Schema-extension entries with allOf-style payload (e.g. fulfillment's
dev.ucp.shopping.checkout) are left untouched.

preprocess_schemas.py also adds response_catalog_schema to the
ucp.json oneOf union; both catalog_search.json and catalog_lookup.json
$ref it.

tests/test_schema_smoke.py — open Total type vocabulary, signed amount
accepting negatives, all four Context fields, open
error/info/warning vocabularies, cart/catalog imports, and an
identity-linking test that instantiates a real `config.scopes`
payload and asserts typed access through ScopePolicy / ScopeToken /
IdentityLinkingBusinessSchema.
tests/test_generate_models.py — invokes generate_models.sh with a
nonexistent SHA in a temp tree and asserts nonzero exit + that
_schema_ref.py wasn't rewritten by the failed run.

CI: matrix workflow on Python 3.10–3.12; pytest>=8 in the dev group.
./generate_models.sh --ref c5c61396ebf9a5afb9c4169bfa3af1409daa6fd6
against Universal-Commerce-Protocol/ucp — the identity-linking OAuth
2.0 foundation merge (PR #354) plus everything from the v2026-04-08
release.

The capability-$defs lift in this PR's preprocessor change collapses
common/identity_linking/dev/ucp/common.py (which used to contain
only `IdentityLinking = RootModel[Any]`) into a flat
common/identity_linking.py exposing typed ScopePolicy, ScopeToken,
Config, IdentityLinkingPlatformSchema, IdentityLinkingBusinessSchema,
and a discriminating IdentityLinking union — i.e. real typed access
to config.scopes / scope_policy. Same lift applies to fulfillment's
dev.ucp.shopping.fulfillment dotted key.

Other additions vs the previous snapshot: Cart (cart.py + create/
update request variants), Catalog Search and Lookup (with
GetProductRequest / GetProductResponse / DetailProduct as nested
defs), the standalone info_code / warning_code / error_response
models, signed_amount with no ge=0 (discounts and refunds carry
negative values), and a wider shopping/types tree covering
adjustment, attribution, available payment instruments, category,
description, media, option / detail option values, pagination,
price, signing keys, signals, and the embedded transport schemas.

Context grew language, currency, and eligibility alongside intent.
Total.type relaxed from a closed Literal to a plain string, matching
the spec's open vocabulary. AP2 mandate, buyer consent, and discount
became package directories instead of single modules due to nested
$defs.

Provenance recorded at src/ucp_sdk/_schema_ref.py.
@caohy1988 caohy1988 force-pushed the regen-ucp-c5c6139 branch from 459c29a to 47fac9c Compare May 6, 2026 19:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant