Skip to content

Commit b1631ad

Browse files
authored
feat!: use type aliases instead of RootModel for root schemas (#51)
* feat!: use type aliases instead of RootModel for root schemas Avoids generating classes with 'root' fields for schemas that don't define an object at the root (like Amount). Instead, they are generated as TypeAliasType using Annotated with Field for validation. This is a breaking change for any client code that was instantiating these root models as classes. * chore: bump version to 0.4.2 * style: fix end of file newlines in generated __init__.py files
1 parent ebd6fc8 commit b1631ad

34 files changed

Lines changed: 637 additions & 759 deletions

generate_models.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ uv run \
8181
--enum-field-as-literal all \
8282
--disable-timestamp \
8383
--use-double-quotes \
84-
--no-use-annotated \
8584
--allow-extra-fields \
85+
--use-type-alias \
8686
--custom-template-dir templates \
8787
--additional-imports pydantic.ConfigDict
8888

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "ucp-sdk"
3-
version = "0.4.1"
3+
version = "0.4.2"
44
description = "UCP Python SDK"
55
readme = "README.md"
66
license = {file = "LICENSE"}

src/ucp_sdk/models/schemas/capability.py

Lines changed: 122 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -18,134 +18,128 @@
1818

1919
from __future__ import annotations
2020

21-
from typing import Any
22-
23-
from pydantic import AnyUrl, BaseModel, ConfigDict, Field, RootModel
24-
25-
26-
class UcpCapability(RootModel[Any]):
27-
model_config = ConfigDict(
28-
frozen=True,
29-
)
30-
root: Any = Field(..., title="UCP Capability")
31-
"""
32-
Schema for UCP capabilities and extensions. Extensions are capabilities with an 'extends' field. Uses reverse-domain naming for governance.
33-
"""
34-
35-
36-
class Extends(RootModel[str]):
37-
model_config = ConfigDict(
38-
frozen=True,
39-
)
40-
root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
41-
"""
42-
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
43-
"""
44-
45-
46-
class Extends1Item(RootModel[str]):
47-
model_config = ConfigDict(
48-
frozen=True,
49-
)
50-
root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
51-
52-
53-
class Extends1(RootModel[list[Extends1Item]]):
54-
model_config = ConfigDict(
55-
frozen=True,
56-
)
57-
root: list[Extends1Item] = Field(..., min_length=1)
58-
"""
59-
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
60-
"""
61-
62-
63-
class Extends2(RootModel[str]):
64-
model_config = ConfigDict(
65-
frozen=True,
66-
)
67-
root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
68-
"""
69-
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
70-
"""
71-
72-
73-
class Extends3Item(RootModel[str]):
74-
model_config = ConfigDict(
75-
frozen=True,
76-
)
77-
root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
78-
79-
80-
class Extends3(RootModel[list[Extends3Item]]):
81-
model_config = ConfigDict(
82-
frozen=True,
83-
)
84-
root: list[Extends3Item] = Field(..., min_length=1)
85-
"""
86-
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
87-
"""
88-
89-
90-
class Extends4(RootModel[str]):
91-
model_config = ConfigDict(
92-
frozen=True,
93-
)
94-
root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
95-
"""
96-
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
97-
"""
98-
99-
100-
class Extends5Item(RootModel[str]):
101-
model_config = ConfigDict(
102-
frozen=True,
103-
)
104-
root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
105-
106-
107-
class Extends5(RootModel[list[Extends5Item]]):
108-
model_config = ConfigDict(
109-
frozen=True,
110-
)
111-
root: list[Extends5Item] = Field(..., min_length=1)
112-
"""
113-
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
114-
"""
115-
116-
117-
class Extends6(RootModel[str]):
118-
model_config = ConfigDict(
119-
frozen=True,
120-
)
121-
root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
122-
"""
123-
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
124-
"""
125-
126-
127-
class Extends7Item(RootModel[str]):
128-
model_config = ConfigDict(
129-
frozen=True,
130-
)
131-
root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
132-
133-
134-
class Extends7(RootModel[list[Extends7Item]]):
135-
model_config = ConfigDict(
136-
frozen=True,
137-
)
138-
root: list[Extends7Item] = Field(..., min_length=1)
139-
"""
140-
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
141-
"""
142-
143-
144-
class Version(RootModel[Any]):
145-
model_config = ConfigDict(
146-
frozen=True,
147-
)
148-
root: Any
21+
from typing import Annotated, Any
22+
23+
from pydantic import AnyUrl, BaseModel, ConfigDict, Field
24+
from typing_extensions import TypeAliasType
25+
26+
UcpCapability = TypeAliasType(
27+
"UcpCapability", Annotated[Any, Field(..., title="UCP Capability")]
28+
)
29+
"""
30+
Schema for UCP capabilities and extensions. Extensions are capabilities with an 'extends' field. Uses reverse-domain naming for governance.
31+
"""
32+
33+
34+
Extends = TypeAliasType(
35+
"Extends",
36+
Annotated[
37+
str, Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
38+
],
39+
)
40+
"""
41+
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
42+
"""
43+
44+
45+
Extends1Item = TypeAliasType(
46+
"Extends1Item",
47+
Annotated[
48+
str, Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
49+
],
50+
)
51+
52+
53+
Extends1 = TypeAliasType(
54+
"Extends1", Annotated[list[Extends1Item], Field(..., min_length=1)]
55+
)
56+
"""
57+
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
58+
"""
59+
60+
61+
Extends2 = TypeAliasType(
62+
"Extends2",
63+
Annotated[
64+
str, Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
65+
],
66+
)
67+
"""
68+
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
69+
"""
70+
71+
72+
Extends3Item = TypeAliasType(
73+
"Extends3Item",
74+
Annotated[
75+
str, Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
76+
],
77+
)
78+
79+
80+
Extends3 = TypeAliasType(
81+
"Extends3", Annotated[list[Extends3Item], Field(..., min_length=1)]
82+
)
83+
"""
84+
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
85+
"""
86+
87+
88+
Extends4 = TypeAliasType(
89+
"Extends4",
90+
Annotated[
91+
str, Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
92+
],
93+
)
94+
"""
95+
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
96+
"""
97+
98+
99+
Extends5Item = TypeAliasType(
100+
"Extends5Item",
101+
Annotated[
102+
str, Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
103+
],
104+
)
105+
106+
107+
Extends5 = TypeAliasType(
108+
"Extends5", Annotated[list[Extends5Item], Field(..., min_length=1)]
109+
)
110+
"""
111+
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
112+
"""
113+
114+
115+
Extends6 = TypeAliasType(
116+
"Extends6",
117+
Annotated[
118+
str, Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
119+
],
120+
)
121+
"""
122+
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
123+
"""
124+
125+
126+
Extends7Item = TypeAliasType(
127+
"Extends7Item",
128+
Annotated[
129+
str, Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$")
130+
],
131+
)
132+
133+
134+
Extends7 = TypeAliasType(
135+
"Extends7", Annotated[list[Extends7Item], Field(..., min_length=1)]
136+
)
137+
"""
138+
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
139+
"""
140+
141+
142+
Version = TypeAliasType("Version", Any)
149143

150144

151145
class Base(BaseModel):

src/ucp_sdk/models/schemas/common/identity_linking.py

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,19 @@
1818

1919
from __future__ import annotations
2020

21-
from typing import Any
21+
from typing import Annotated, Any
2222

23-
from pydantic import BaseModel, ConfigDict, Field, RootModel
23+
from pydantic import BaseModel, ConfigDict, Field
24+
from typing_extensions import TypeAliasType
2425

2526
from ..shopping.types import description as description_1
2627

27-
28-
class IdentityLinking(RootModel[Any]):
29-
model_config = ConfigDict(
30-
frozen=True,
31-
)
32-
root: Any = Field(..., title="Identity Linking")
33-
"""
34-
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.
35-
"""
28+
IdentityLinking = TypeAliasType(
29+
"IdentityLinking", Annotated[Any, Field(..., title="Identity Linking")]
30+
)
31+
"""
32+
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.
33+
"""
3634

3735

3836
class ScopePolicy(BaseModel):
@@ -49,20 +47,19 @@ class ScopePolicy(BaseModel):
4947
"""
5048

5149

52-
class ScopeToken(RootModel[str]):
53-
model_config = ConfigDict(
54-
frozen=True,
55-
)
56-
root: str = Field(
57-
..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+:[a-z][a-z0-9_]*$"
58-
)
59-
"""
60-
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.
61-
"""
50+
ScopeToken = TypeAliasType(
51+
"ScopeToken",
52+
Annotated[
53+
str,
54+
Field(
55+
...,
56+
pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+:[a-z][a-z0-9_]*$",
57+
),
58+
],
59+
)
60+
"""
61+
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.
62+
"""
6263

6364

64-
class IdentityLinking1(RootModel[Any]):
65-
model_config = ConfigDict(
66-
frozen=True,
67-
)
68-
root: Any
65+
IdentityLinking1 = TypeAliasType("IdentityLinking1", Any)

src/ucp_sdk/models/schemas/payment_handler.py

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,22 @@
1818

1919
from __future__ import annotations
2020

21-
from typing import Any
21+
from typing import Annotated, Any
2222

23-
from pydantic import AnyUrl, BaseModel, ConfigDict, Field, RootModel
23+
from pydantic import AnyUrl, BaseModel, ConfigDict, Field
24+
from typing_extensions import TypeAliasType
2425

2526
from .shopping.types import available_payment_instrument
2627

28+
PaymentHandler = TypeAliasType(
29+
"PaymentHandler", Annotated[Any, Field(..., title="Payment Handler")]
30+
)
31+
"""
32+
Schema for UCP payment handlers. Handlers define how payment instruments are processed.
33+
"""
2734

28-
class PaymentHandler(RootModel[Any]):
29-
model_config = ConfigDict(
30-
frozen=True,
31-
)
32-
root: Any = Field(..., title="Payment Handler")
33-
"""
34-
Schema for UCP payment handlers. Handlers define how payment instruments are processed.
35-
"""
3635

37-
38-
class Version(RootModel[Any]):
39-
model_config = ConfigDict(
40-
frozen=True,
41-
)
42-
root: Any
36+
Version = TypeAliasType("Version", Any)
4337

4438

4539
class Base(BaseModel):

0 commit comments

Comments
 (0)