Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ for item in checkout.line_items:

| Package | Description |
| --------------------------------------- | --------------------------------------------------- |
| `ucp_sdk.models.schemas.shopping` | Checkout, cart, order, payment models |
| `ucp_sdk.models.schemas.shopping.types` | Line items, totals, buyer, fulfillment, etc. |
| `ucp_sdk.models.schemas.shopping` | Checkout, cart, catalog, order, payment models |
| `ucp_sdk.models.schemas.shopping.types` | Line items, totals, buyer, fulfillment, signals |
| `ucp_sdk.models.schemas.transports` | REST, MCP, and embedded protocol bindings |
| `ucp_sdk.models.schemas` | Service definitions, capabilities, payment handlers |

Expand Down Expand Up @@ -122,7 +122,7 @@ uv sync
```

Where `<version>` is the version of the UCP specification to use (for example,
"2026-01-23").
"2026-04-08").

If no version is specified, the `main` branch of the
[UCP repo](https://github.com/Universal-Commerce-Protocol/ucp) will be used.
Expand Down
4 changes: 2 additions & 2 deletions preprocess_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ def update_variant_identity(variant_schema, op, stem):
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
Example: product.json -> product_create_request.json.
"""
for node in iter_nodes(root):
if isinstance(node, dict) and "$ref" in node:
Expand Down Expand Up @@ -494,7 +494,7 @@ def main():
1. Pass 1: Local flattening (allOf) and discovery of needed variants
2. metadata normalization: unifies ucp properties
3. Pass 2: Transitive propagation (ensuring matched variants for linked schemas)
4. Pass 3: Variant file generation (*_request.json)
4. Pass 3: Variant file generation (*_request.json).
"""
target_dir = Path(
sys.argv[1] if len(sys.argv) > 1 else "ucp/source/schemas"
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "ucp-sdk"
version = "0.3.0"
version = "0.4.0"
description = "UCP Python SDK"
readme = "README.md"
license = {file = "LICENSE"}
Expand Down Expand Up @@ -68,6 +68,9 @@ indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"

[tool.ruff.lint.per-file-ignores]
"src/ucp_sdk/models/schemas/**/*.py" = ["E501", "D"]

[tool.ruff.lint]
select = ["E", "F", "W", "B", "C4", "SIM", "N", "UP", "D", "PTH", "T20"]
ignore = ["D212", "D200"]
Expand Down
1 change: 1 addition & 0 deletions src/ucp_sdk/models/schemas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
# generated by datamodel-codegen
# pylint: disable=all
# pyformat: disable

132 changes: 116 additions & 16 deletions src/ucp_sdk/models/schemas/capability.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,114 @@ class UcpCapability(RootModel[Any]):
"""


class Extends(RootModel[str]):
model_config = ConfigDict(
frozen=True,
)
root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
"""
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
"""


class Extends1Item(RootModel[str]):
model_config = ConfigDict(
frozen=True,
)
root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")


class Extends1(RootModel[list[Extends1Item]]):
model_config = ConfigDict(
frozen=True,
)
root: list[Extends1Item] = Field(..., min_length=1)
"""
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
"""


class Extends2(RootModel[str]):
model_config = ConfigDict(
frozen=True,
)
root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
"""
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
"""


class Extends3Item(RootModel[str]):
model_config = ConfigDict(
frozen=True,
)
root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")


class Extends3(RootModel[list[Extends3Item]]):
model_config = ConfigDict(
frozen=True,
)
root: list[Extends3Item] = Field(..., min_length=1)
"""
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
"""


class Extends4(RootModel[str]):
model_config = ConfigDict(
frozen=True,
)
root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
"""
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
"""


class Extends5Item(RootModel[str]):
model_config = ConfigDict(
frozen=True,
)
root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")


class Extends5(RootModel[list[Extends5Item]]):
model_config = ConfigDict(
frozen=True,
)
root: list[Extends5Item] = Field(..., min_length=1)
"""
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
"""


class Extends6(RootModel[str]):
model_config = ConfigDict(
frozen=True,
)
root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
"""
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
"""


class Extends7Item(RootModel[str]):
model_config = ConfigDict(
frozen=True,
)
root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")


class Extends7(RootModel[list[Extends7Item]]):
model_config = ConfigDict(
frozen=True,
)
root: list[Extends7Item] = Field(..., min_length=1)
"""
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
"""


class Version(RootModel[Any]):
model_config = ConfigDict(
frozen=True,
Expand Down Expand Up @@ -64,11 +172,9 @@ class Base(BaseModel):
"""
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_]*)+$"
)
extends: Extends | Extends1 | None = None
"""
Parent capability this extends. Present for extensions, absent for root capabilities.
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
"""


Expand Down Expand Up @@ -100,11 +206,9 @@ class PlatformSchema(BaseModel):
"""
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_]*)+$"
)
extends: Extends2 | Extends3 | None = None
"""
Parent capability this extends. Present for extensions, absent for root capabilities.
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
"""


Expand Down Expand Up @@ -136,11 +240,9 @@ class BusinessSchema(BaseModel):
"""
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_]*)+$"
)
extends: Extends4 | Extends5 | None = None
"""
Parent capability this extends. Present for extensions, absent for root capabilities.
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
"""


Expand Down Expand Up @@ -172,9 +274,7 @@ class ResponseSchema(BaseModel):
"""
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_]*)+$"
)
extends: Extends6 | Extends7 | None = None
"""
Parent capability this extends. Present for extensions, absent for root capabilities.
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
"""
18 changes: 18 additions & 0 deletions src/ucp_sdk/models/schemas/common/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2026 UCP Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# generated by datamodel-codegen
# pylint: disable=all
# pyformat: disable

61 changes: 61 additions & 0 deletions src/ucp_sdk/models/schemas/common/identity_linking/__init__.py
Original file line number Diff line number Diff line change
@@ -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 Any

from pydantic import BaseModel, ConfigDict, Field, RootModel

from ...shopping.types import description as description_1


class IdentityLinking(RootModel[Any]):
model_config = ConfigDict(
frozen=True,
)
root: Any = Field(..., title="Identity Linking")
"""
Capability schema for identity linking. Businesses declare the user-authenticated scopes they offer in a flat 'scopes' map. Each key is the OAuth scope string as it appears on the wire ('{capability}:{scope}', e.g. 'dev.ucp.shopping.order:read'). Scope presence implies that the corresponding operations require user authentication. Operations not gated by any listed scope operate at whatever access level the business permits; UCP does not prescribe a default.
"""


class ScopePolicy(BaseModel):
"""
Per-scope policy and metadata — auth constraints (e.g. min_acr, max_token_age), declarative metadata (e.g. claims produced, consent descriptions), or any other scope-specific configuration. An empty object means user authentication is required with no additional policy. Open for non-breaking extension.
"""

model_config = ConfigDict(
extra="allow",
)
description: description_1.Description | None = None
"""
Optional human-readable description of the scope that platforms can use to present and explain context (requirement and value) to the user.
"""


class ScopeToken(RootModel[str]):
model_config = ConfigDict(
frozen=True,
)
root: str = Field(
..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+:[a-z][a-z0-9_]*$"
)
"""
OAuth scope string formed by joining a capability name and a scope name with a colon: '{capability}:{scope}', e.g. 'dev.ucp.shopping.order:read'. Capability names use reverse-DNS naming; scope names denote the permission granted, defined by each capability's spec (e.g. 'read', 'manage', 'create'). Platforms request these strings verbatim in OAuth 'scope' parameters; issued tokens carry them in the 'scope' claim.
"""
18 changes: 18 additions & 0 deletions src/ucp_sdk/models/schemas/common/identity_linking/dev/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2026 UCP Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# generated by datamodel-codegen
# pylint: disable=all
# pyformat: disable

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2026 UCP Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# generated by datamodel-codegen
# pylint: disable=all
# pyformat: disable

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 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, RootModel


class IdentityLinking(RootModel[Any]):
model_config = ConfigDict(
frozen=True,
)
root: Any
Loading
Loading