Skip to content
Closed
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
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright [yyyy] [name of copyright owner]
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.
Expand Down
19 changes: 15 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
[project]
name = "ucp-sdk"
version = "0.0.3"
version = "0.3"
description = "UCP Python SDK"
readme = "README.md"
license-files = ["LICENSE"]
license = {file = "LICENSE"}
authors = [
{ name = "Florin Iucha", email = "fiucha@google.com" },
{ name = "Federico D'Amato", email = "damaz@google.com" },
{ name = "Enric Cusell", email = "cusell@google.com" },
{ name = "Federico D'Amato", email = "damaz@google.com" }
]
classifiers = [
"License :: OSI Approved :: Apache Software License",
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries :: Python Modules",
]
requires-python = ">=3.10"
dependencies = [
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.
"""
Comment on lines +36 to +141
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is significant duplication of Extends types (e.g., Extends, Extends2, Extends4, Extends6 are identical RootModel[str] definitions). This appears to be an artifact of the code generation process. Consolidating these into a single reusable type would improve the maintainability and readability of the SDK models.



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

114 changes: 114 additions & 0 deletions src/ucp_sdk/models/schemas/common/identity_linking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# 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

from ..capability import BusinessSchema as BusinessSchema_1
from ..capability import PlatformSchema as PlatformSchema_1


class IdentityLinkingCapability(RootModel[Any]):
model_config = ConfigDict(
frozen=True,
)
root: Any = Field(..., title="Identity Linking Capability")
"""
Schema for authenticating and establishing verified connections between platforms and businesses.
"""


class IdentityScope(RootModel[str]):
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_\\-]*)*\\.scopes\\.[a-zA-Z0-9][a-zA-Z0-9_\\-]*(\\.[a-zA-Z0-9][a-zA-Z0-9_\\-]*)*$",
)


class IdentityScopes(RootModel[list[IdentityScope]]):
"""
A custom UCP annotation placed at the root of a capability schema to declare the OAuth 2.0 scopes required to operate that capability. Scopes MUST use reverse DNS dot notation to prevent namespace collisions (e.g., 'dev.ucp.shopping.scopes.checkout_session' for UCP-defined scopes, 'com.example.scopes.my_capability' for third-party scopes). Platforms collect this annotation from every capability in the finalized negotiated intersection and use the union as the authorization scope set. This annotation is intentionally a plain JSON array so it is ignored by standard JSON Schema validators — it carries semantic meaning only to UCP-aware tooling. Absence of this annotation on a capability schema means that capability requires no dedicated scope.
"""

model_config = ConfigDict(
frozen=True,
)
root: list[IdentityScope] = Field(
..., min_length=1, title="Identity Scopes Annotation"
)
"""
A custom UCP annotation placed at the root of a capability schema to declare the OAuth 2.0 scopes required to operate that capability. Scopes MUST use reverse DNS dot notation to prevent namespace collisions (e.g., 'dev.ucp.shopping.scopes.checkout_session' for UCP-defined scopes, 'com.example.scopes.my_capability' for third-party scopes). Platforms collect this annotation from every capability in the finalized negotiated intersection and use the union as the authorization scope set. This annotation is intentionally a plain JSON array so it is ignored by standard JSON Schema validators — it carries semantic meaning only to UCP-aware tooling. Absence of this annotation on a capability schema means that capability requires no dedicated scope.
"""


class Mechanism(BaseModel):
"""
Base definition for any authentication mechanism. The 'type' field discriminates between known mechanism schemas (e.g., oauth2). Unknown types pass through with only the base requirement, enabling forward-compatible extensibility. Note: this open base schema does not enforce field requirements for known types — use $defs/oauth2 directly to validate an oauth2 mechanism object explicitly.
"""

model_config = ConfigDict(
extra="allow",
)
type: str
"""
The mechanism type discriminator. Known values: 'oauth2'. Specific mechanism schemas constrain this to a constant value.
"""


class Oauth2(BaseModel):
model_config = ConfigDict(
extra="allow",
)
type: Literal["oauth2"]
"""
OAuth 2.0 authentication mechanism.
"""
issuer: AnyUrl
"""
The authorization server URL, supporting RFC 8414 discovery.
"""
discovery_endpoint: AnyUrl | None = None
"""
Optional explicit URI to the authorization server's metadata (e.g., `https://auth.merchant.example.com/.well-known/openid-configuration`). If omitted, platforms construct discovery paths based on the `issuer`.
"""


class PlatformSchema(PlatformSchema_1):
model_config = ConfigDict(
extra="allow",
)


class Config(BaseModel):
model_config = ConfigDict(
extra="allow",
)
supported_mechanisms: list[Mechanism] = Field(..., min_length=1)


class BusinessSchema(BusinessSchema_1):
model_config = ConfigDict(
extra="allow",
)
config: Config
Loading
Loading