Skip to content
Open
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
21 changes: 15 additions & 6 deletions dargs/dargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1165,8 +1165,9 @@ def _resolve_ref(d: dict, allow_ref: bool = False) -> None:
The file is loaded and its contents are merged into ``d``. Keys already
present in ``d`` (other than ``$ref``) take precedence over keys from the
loaded file, allowing local overrides. Chained ``$ref`` values in the
loaded content are resolved in turn. Cyclic references are detected and
raise a ``ValueError``.
loaded content are resolved in turn. Relative paths in a chain are resolved
from the directory of the file that contains them. Cyclic references are
detected and raise a ``ValueError``.

The dict is modified **in place**.

Expand All @@ -1192,12 +1193,20 @@ def _resolve_ref(d: dict, allow_ref: bool = False) -> None:
"Pass allow_ref=True to enable loading from external files."
)
visited_refs: set[str] = set()
base_dir = os.curdir
while "$ref" in d:
ref_path = d.pop("$ref")
if ref_path in visited_refs:
raise ValueError(f"Cyclic $ref detected for path: {ref_path!r}")
visited_refs.add(ref_path)
loaded = _load_ref(ref_path)
resolved_ref_path = (
ref_path if os.path.isabs(ref_path) else os.path.join(base_dir, ref_path)
)
canonical_ref_path = os.path.realpath(resolved_ref_path)
if canonical_ref_path in visited_refs:
raise ValueError(f"Cyclic $ref detected for path: {canonical_ref_path!r}")
visited_refs.add(canonical_ref_path)
loaded = _load_ref(canonical_ref_path)
# A chained relative reference belongs to the file that declared it,
# rather than to the process's current working directory.
base_dir = os.path.dirname(canonical_ref_path)
# Merge: loaded content as base, local keys take precedence
merged = {**loaded, **d}
d.clear()
Expand Down
9 changes: 6 additions & 3 deletions tests/test_ref.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,12 @@ def test_ref_cyclic_detection(self) -> None:
ca.check({"base": {"$ref": ref_path}}, allow_ref=True)

def test_ref_chained(self) -> None:
"""A $ref that loads a file containing another $ref is fully resolved."""
inner_path = self._write_json("ref_inner.json", {"sub1": 7, "sub2": "inner"})
outer_path = self._write_json("ref_outer.json", {"$ref": inner_path})
"""A nested relative $ref resolves beside the file that declares it."""
self._write_json("ref_inner.json", {"sub1": 7, "sub2": "inner"})
outer_path = self._write_json(
"ref_outer.json",
{"$ref": "ref_inner.json"},
)
ca = Argument(
"base",
dict,
Expand Down
Loading