Skip to content

Commit d650f0b

Browse files
authored
fix: resolve entity local references before inlining (#72) (#79)
* fix: resolve entity local references before inlining (#72) Recursively resolve same-document $refs on the entity definition (such as #/$defs/version) upon extraction in preprocess_schemas.py before inlining into capability.json, service.json, and payment_handler.json. This prevents dangling references in preprocessed schemas, enables valid pattern constraints on generated models, and eliminates Version = TypeAliasType("Version", Any). Fixes #72 * chore: bump version to 0.4.5 and update authors
1 parent d790602 commit d650f0b

6 files changed

Lines changed: 245 additions & 93 deletions

File tree

preprocess_schemas.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,37 @@ def resolve_local_ref(ref, root):
8686
return current
8787

8888

89+
def resolve_local_refs(fragment, root, seen=None):
90+
"""
91+
Recursively resolves and inlines local $ref pointers (#/...) within a schema fragment.
92+
"""
93+
if seen is None:
94+
seen = set()
95+
96+
if isinstance(fragment, dict):
97+
if "$ref" in fragment:
98+
ref = fragment["$ref"]
99+
if (
100+
isinstance(ref, str)
101+
and ref.startswith("#/")
102+
and ref not in seen
103+
):
104+
target = resolve_local_ref(ref, root)
105+
if target is not None:
106+
resolved = copy.deepcopy(target)
107+
resolve_local_refs(resolved, root, seen | {ref})
108+
for k, v in fragment.items():
109+
if k != "$ref":
110+
resolved[k] = v
111+
fragment.clear()
112+
fragment.update(resolved)
113+
for v in list(fragment.values()):
114+
resolve_local_refs(v, root, seen)
115+
elif isinstance(fragment, list):
116+
for item in fragment:
117+
resolve_local_refs(item, root, seen)
118+
119+
89120
# --- Schema Normalization and Flattening ---
90121

91122

@@ -679,7 +710,10 @@ def main():
679710
ucp_path = str((target_dir / "ucp.json").resolve())
680711
entity_def = {}
681712
if ucp_path in schemas:
682-
entity_def = schemas[ucp_path].get("$defs", {}).get("entity", {})
713+
entity_def = copy.deepcopy(
714+
schemas[ucp_path].get("$defs", {}).get("entity", {})
715+
)
716+
resolve_local_refs(entity_def, schemas[ucp_path])
683717
if not entity_def:
684718
raise ValueError(
685719
"Entity definition not found! 'ucp.json' must define '$defs.entity'"

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
[project]
22
name = "ucp-sdk"
3-
version = "0.4.4"
3+
version = "0.4.5"
44
description = "UCP Python SDK"
55
readme = "README.md"
66
license = {file = "LICENSE"}
77
authors = [
8-
{ name = "Enric Cusell", email = "cusell@google.com" },
98
{ name = "Federico D'Amato", email = "damaz@google.com" }
109
]
1110
classifiers = [

src/ucp_sdk/models/schemas/capability.py

Lines changed: 46 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -58,56 +58,11 @@
5858
"""
5959

6060

61-
Extends2 = TypeAliasType("Extends2", Extends)
62-
63-
64-
Extends3Item = TypeAliasType("Extends3Item", Extends1Item)
65-
66-
67-
Extends3 = TypeAliasType(
68-
"Extends3", Annotated[list[Extends3Item], Field(..., min_length=1)]
69-
)
70-
"""
71-
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
72-
"""
73-
74-
75-
Extends4 = TypeAliasType("Extends4", Extends)
76-
77-
78-
Extends5Item = TypeAliasType("Extends5Item", Extends1Item)
79-
80-
81-
Extends5 = TypeAliasType(
82-
"Extends5", Annotated[list[Extends5Item], Field(..., min_length=1)]
83-
)
84-
"""
85-
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
86-
"""
87-
88-
89-
Extends6 = TypeAliasType("Extends6", Extends)
90-
91-
92-
Extends7Item = TypeAliasType("Extends7Item", Extends1Item)
93-
94-
95-
Extends7 = TypeAliasType(
96-
"Extends7", Annotated[list[Extends7Item], Field(..., min_length=1)]
97-
)
98-
"""
99-
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
100-
"""
101-
102-
103-
Version = TypeAliasType("Version", Any)
104-
105-
10661
class Base(BaseModel):
10762
model_config = ConfigDict(
10863
extra="allow",
10964
)
110-
version: Version
65+
version: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$")
11166
"""
11267
Entity version in YYYY-MM-DD format.
11368
"""
@@ -133,6 +88,20 @@ class Base(BaseModel):
13388
"""
13489

13590

91+
Extends2 = TypeAliasType("Extends2", Extends)
92+
93+
94+
Extends3Item = TypeAliasType("Extends3Item", Extends1Item)
95+
96+
97+
Extends3 = TypeAliasType(
98+
"Extends3", Annotated[list[Extends3Item], Field(..., min_length=1)]
99+
)
100+
"""
101+
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
102+
"""
103+
104+
136105
class PlatformSchema(BaseModel):
137106
"""
138107
Full capability declaration for platform-level discovery. Includes spec/schema URLs for agent fetching.
@@ -141,7 +110,7 @@ class PlatformSchema(BaseModel):
141110
model_config = ConfigDict(
142111
extra="allow",
143112
)
144-
version: Version
113+
version: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$")
145114
"""
146115
Entity version in YYYY-MM-DD format.
147116
"""
@@ -167,6 +136,20 @@ class PlatformSchema(BaseModel):
167136
"""
168137

169138

139+
Extends4 = TypeAliasType("Extends4", Extends)
140+
141+
142+
Extends5Item = TypeAliasType("Extends5Item", Extends1Item)
143+
144+
145+
Extends5 = TypeAliasType(
146+
"Extends5", Annotated[list[Extends5Item], Field(..., min_length=1)]
147+
)
148+
"""
149+
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
150+
"""
151+
152+
170153
class BusinessSchema(BaseModel):
171154
"""
172155
Capability configuration for business/merchant level. May include business-specific config overrides.
@@ -175,7 +158,7 @@ class BusinessSchema(BaseModel):
175158
model_config = ConfigDict(
176159
extra="allow",
177160
)
178-
version: Version
161+
version: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$")
179162
"""
180163
Entity version in YYYY-MM-DD format.
181164
"""
@@ -201,6 +184,20 @@ class BusinessSchema(BaseModel):
201184
"""
202185

203186

187+
Extends6 = TypeAliasType("Extends6", Extends)
188+
189+
190+
Extends7Item = TypeAliasType("Extends7Item", Extends1Item)
191+
192+
193+
Extends7 = TypeAliasType(
194+
"Extends7", Annotated[list[Extends7Item], Field(..., min_length=1)]
195+
)
196+
"""
197+
Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions.
198+
"""
199+
200+
204201
class ResponseSchema(BaseModel):
205202
"""
206203
Capability reference in responses. Only name/version required to confirm active capabilities.
@@ -209,7 +206,7 @@ class ResponseSchema(BaseModel):
209206
model_config = ConfigDict(
210207
extra="allow",
211208
)
212-
version: Version
209+
version: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$")
213210
"""
214211
Entity version in YYYY-MM-DD format.
215212
"""

src/ucp_sdk/models/schemas/payment_handler.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,11 @@
3333
"""
3434

3535

36-
Version = TypeAliasType("Version", Any)
37-
38-
3936
class Base(BaseModel):
4037
model_config = ConfigDict(
4138
extra="allow",
4239
)
43-
version: Version
40+
version: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$")
4441
"""
4542
Entity version in YYYY-MM-DD format.
4643
"""
@@ -76,7 +73,7 @@ class PlatformSchema(BaseModel):
7673
model_config = ConfigDict(
7774
extra="allow",
7875
)
79-
version: Version
76+
version: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$")
8077
"""
8178
Entity version in YYYY-MM-DD format.
8279
"""
@@ -112,7 +109,7 @@ class BusinessSchema(BaseModel):
112109
model_config = ConfigDict(
113110
extra="allow",
114111
)
115-
version: Version
112+
version: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$")
116113
"""
117114
Entity version in YYYY-MM-DD format.
118115
"""
@@ -148,7 +145,7 @@ class ResponseSchema(BaseModel):
148145
model_config = ConfigDict(
149146
extra="allow",
150147
)
151-
version: Version
148+
version: str = Field(..., pattern="^\\d{4}-\\d{2}-\\d{2}$")
152149
"""
153150
Entity version in YYYY-MM-DD format.
154151
"""

0 commit comments

Comments
 (0)