Skip to content

Commit bcda715

Browse files
committed
fix(preprocess): flatten dotted $defs names to prevent path-leak codegen
Upstream UCP uses reverse-DNS extension mount points such as "dev.ucp.shopping.checkout" in $defs. datamodel-codegen treats dots in def names as path separators, so those defs were being emitted as nested directory trees (e.g. shopping/fulfillment/dev/ucp/shopping.py) instead of inline classes in the parent schema's module. This added a normalization pass that renames such keys before codegen. Strategy: prefer the last dotted component (giving a clean class name like "Checkout"); fall back to dot-replaced-with-underscore if the bare tail would collide with an existing def in the same file (the fulfillment.json case, where the dotted key shares its tail with a sibling "fulfillment" def). Local $ref pointers to renamed defs are rewritten in the same pass so no internal references break. No upstream schemas currently reference the dotted defs by $ref; the rewrite is a safety net. Effect on regen: extension classes return to their natural import paths. For example, ucp_sdk.models.schemas.shopping.fulfillment.Checkout was being relocated to ucp_sdk.models.schemas.shopping.fulfillment.dev.ucp. shopping.Checkout under current upstream; this restores the flat path.
1 parent a79c66e commit bcda715

1 file changed

Lines changed: 64 additions & 0 deletions

File tree

preprocess_schemas.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,69 @@ def preprocess_full_schema(schema, entity_def=None):
267267
distribute_properties_to_branches(node)
268268

269269

270+
# --- Dotted $defs Flattening ---
271+
272+
273+
def _rewrite_local_defs_refs(node, rename_map):
274+
"""Walks a schema tree and rewrites local $defs refs whose target was renamed."""
275+
prefix = "#/$defs/"
276+
for n in iter_nodes(node):
277+
if not isinstance(n, dict):
278+
continue
279+
ref = n.get("$ref")
280+
if not isinstance(ref, str) or not ref.startswith(prefix):
281+
continue
282+
rest = ref[len(prefix) :]
283+
name, sep, tail = rest.partition("/")
284+
if name in rename_map:
285+
n["$ref"] = prefix + rename_map[name] + (sep + tail if sep else "")
286+
287+
288+
def flatten_dotted_defs(schema):
289+
"""
290+
Renames $defs keys containing '.' so codegen does not emit nested directories.
291+
292+
RATIONALE: datamodel-codegen treats dots in $def names as path separators,
293+
so a definition like 'dev.ucp.shopping.checkout' produces
294+
'dev/ucp/shopping/checkout.py' rather than a class in the parent module.
295+
UCP uses reverse-DNS def names as extension mount points (e.g. an extension
296+
schema's contribution to the base Checkout type), so those classes belong
297+
inline with the rest of the schema's output.
298+
299+
Strategy: prefer the last dotted component as the new key (giving a clean
300+
class name like 'Checkout'); fall back to dot-replaced-with-underscore
301+
(e.g. 'DevUcpShoppingFulfillment') if the bare tail would collide with
302+
an existing def in the same file.
303+
"""
304+
defs = schema.get("$defs")
305+
if not isinstance(defs, dict):
306+
return
307+
308+
existing = set(defs.keys())
309+
rename_map = {}
310+
for old in list(defs.keys()):
311+
if "." not in old:
312+
continue
313+
tail = old.rsplit(".", 1)[-1]
314+
if tail and tail not in existing:
315+
new = tail
316+
else:
317+
new = old.replace(".", "_")
318+
if new in existing:
319+
# Both candidates collide; leave as-is rather than risk corruption
320+
continue
321+
rename_map[old] = new
322+
existing.discard(old)
323+
existing.add(new)
324+
325+
if not rename_map:
326+
return
327+
328+
for old, new in rename_map.items():
329+
defs[new] = defs.pop(old)
330+
_rewrite_local_defs_refs(schema, rename_map)
331+
332+
270333
# --- Variant Generation (Create/Update/Complete) ---
271334

272335

@@ -530,6 +593,7 @@ def main():
530593
for p_abs, s in schemas.items():
531594
if "ucp.json" in p_abs or "_request.json" in p_abs:
532595
continue
596+
flatten_dotted_defs(s)
533597
preprocess_full_schema(s, entity_def)
534598
# Write back the flattened core schema
535599
save_json(s, Path(p_abs))

0 commit comments

Comments
 (0)