Skip to content

Commit 025b8eb

Browse files
committed
ContextBuilder: replace copy_resolved_props with direct ref assignment
Instead of copying all resolved properties on $ref cache hits and then collapsing duplicates in _dedup_to_defs, record the (src, dst, key) tuple and set a ref pointer directly after hoisting. This avoids exponential growth when a widely-used schema is cached at the root level (src_path=[]), which previously caused copy_resolved_props to duplicate the entire _resolved_properties dict on every hit.
1 parent ea9ac21 commit 025b8eb

1 file changed

Lines changed: 15 additions & 42 deletions

File tree

ogc/na/annotate_schema.py

Lines changed: 15 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -933,32 +933,7 @@ def read_properties(subschema: dict, from_schema: ReferencedSchema,
933933

934934
cached_schema_contexts = {}
935935
cached_schema_paths: dict[str, list[str]] = {}
936-
copy_log: list[tuple[list[str], list[str], str]] = []
937-
938-
# ------------------------------------------------------------------
939-
940-
def copy_resolved_props(src_path: list[str], dst_path: list[str]) -> None:
941-
"""
942-
Copy all ResolvedProperty entries rooted at *src_path* to *dst_path*.
943-
Fallback for $ref cache-hits at unnamed nodes (allOf / root) where there
944-
is no rp entry to hang a `ref` pointer on.
945-
"""
946-
if src_path == dst_path:
947-
return
948-
sep = '\x00'
949-
src_len = len(src_path)
950-
for k, v in list(self._resolved_properties.items()):
951-
if len(v.path) > src_len and v.path[:src_len] == src_path:
952-
new_path = dst_path + v.path[src_len:]
953-
new_key = sep.join(new_path)
954-
if new_key in self._resolved_properties:
955-
merged = copy.deepcopy(v)
956-
merged.path = new_path
957-
self._resolved_properties[new_key].merge(merged)
958-
else:
959-
new_rp = copy.deepcopy(v)
960-
new_rp.path = new_path
961-
self._resolved_properties[new_key] = new_rp
936+
ref_log: list[tuple[list[str], list[str], str]] = []
962937

963938
# ------------------------------------------------------------------
964939

@@ -999,8 +974,8 @@ def process_subschema(subschema: dict, from_schema: ReferencedSchema,
999974
else:
1000975
if cache_key in cached_schema_paths:
1001976
src = cached_schema_paths[cache_key]
1002-
copy_resolved_props(src, schema_path)
1003-
copy_log.append((list(src), list(schema_path), cache_key))
977+
if src and schema_path:
978+
ref_log.append((list(src), list(schema_path), cache_key))
1004979
merge_contexts(onto_context, ref_ctx)
1005980
else:
1006981
# local_refs_only blocks this ref's context contribution, but we still
@@ -1152,7 +1127,7 @@ def _flatten(bl):
11521127

11531128
merge_contexts(own_context, process_subschema(root_schema.subschema, root_schema, []))
11541129
self._hoist_common_branch_properties()
1155-
self._dedup_to_defs(copy_log)
1130+
self._dedup_to_defs(ref_log)
11561131
self._renumber_branches()
11571132

11581133
for imported_et in imported_extra_terms.values():
@@ -1406,14 +1381,14 @@ def _all_descendants(key: str) -> list[tuple[str, ResolvedProperty]]:
14061381
elif group_key in rp:
14071382
_del(group_key)
14081383

1409-
def _dedup_to_defs(self, copy_log: list[tuple[list[str], list[str], str]]) -> None:
1384+
def _dedup_to_defs(self, ref_log: list[tuple[list[str], list[str], str]]) -> None:
14101385
"""
1411-
Post-hoist deduplication: for each (src_path, dst_path, cache_key) in copy_log,
1412-
if the dst subtree still has entries (not all hoisted), move the src subtree
1413-
into _resolved_property_defs[cache_key] with relative paths and replace both
1414-
the src and dst nodes with ``ref`` pointers.
1386+
For each (src_path, dst_path, cache_key) recorded at $ref cache-hit time,
1387+
move the src subtree into _resolved_property_defs[cache_key] with relative
1388+
paths (first occurrence only) and set a ``ref`` pointer on both the src node
1389+
and the dst node. No pre-copying is needed — dst has no inline children.
14151390
"""
1416-
if not copy_log:
1391+
if not ref_log:
14171392
return
14181393

14191394
rp = self._resolved_properties
@@ -1446,19 +1421,18 @@ def _remove_descendants(path: list[str]) -> None:
14461421
children.pop(k, None)
14471422
children.pop(path_key, None)
14481423

1449-
for src_path, dst_path, cache_key in copy_log:
1424+
for src_path, dst_path, cache_key in ref_log:
14501425
if not src_path or not dst_path or src_path == dst_path:
14511426
continue
14521427

14531428
dst_key = sep.join(dst_path)
1454-
dst_descendants = _get_descendants(dst_path)
1455-
if not dst_descendants:
1456-
continue # all hoisted away; nothing to dedup
1429+
if dst_key not in rp:
1430+
continue
14571431

14581432
if cache_key not in self._resolved_property_defs:
14591433
src_descendants = _get_descendants(src_path)
14601434
if not src_descendants:
1461-
continue
1435+
continue # all hoisted away; nothing to put in a def
14621436
src_len = len(src_path)
14631437
def_entries = []
14641438
for _, v in src_descendants:
@@ -1471,8 +1445,7 @@ def _remove_descendants(path: list[str]) -> None:
14711445
if src_key in rp:
14721446
rp[src_key].ref = cache_key
14731447

1474-
_remove_descendants(dst_path)
1475-
if dst_key in rp:
1448+
if dst_key in rp: # may have been removed if dst was under src
14761449
rp[dst_key].ref = cache_key
14771450

14781451
def _renumber_branches(self) -> None:

0 commit comments

Comments
 (0)