Navigate nested structure via dotted path and update value.
Why
CLI overrides like --cv.sections.education.0.institution MIT
must modify deeply nested YAML values without requiring users
to edit files. Recursive traversal handles arbitrary nesting
with proper index validation and error context.
Example
data = {"cv": {"sections": {"education": [{"institution": "A"}]}}}
result = update_value_by_location(
data,
"cv.sections.education.0.institution",
"MIT",
"cv.sections.education.0.institution",
)
assert result["cv"]["sections"]["education"][0]["institution"] == "MIT"
Parameters:
-
dict_or_list
(T)
–
Target structure to modify.
-
key
(str)
–
Remaining path segments to traverse.
-
value
(str)
–
-
full_key
(str)
–
Original full path for error messages.
Returns:
Source code in src/rendercv/schema/override_dictionary.py
| def update_value_by_location[T: dict | list](
dict_or_list: T,
key: str,
value: str,
full_key: str,
) -> T:
"""Navigate nested structure via dotted path and update value.
Why:
CLI overrides like `--cv.sections.education.0.institution MIT`
must modify deeply nested YAML values without requiring users
to edit files. Recursive traversal handles arbitrary nesting
with proper index validation and error context.
Example:
```py
data = {"cv": {"sections": {"education": [{"institution": "A"}]}}}
result = update_value_by_location(
data,
"cv.sections.education.0.institution",
"MIT",
"cv.sections.education.0.institution",
)
assert result["cv"]["sections"]["education"][0]["institution"] == "MIT"
```
Args:
dict_or_list: Target structure to modify.
key: Remaining path segments to traverse.
value: Replacement value.
full_key: Original full path for error messages.
Returns:
Modified structure.
"""
keys = key.split(".")
first_key: str | int = keys[0]
remaining_key = ".".join(keys[1:])
# Calculate the parent path for error messages
processed_count = len(full_key.split(".")) - len(key.split("."))
previous_key = (
".".join(full_key.split(".")[:processed_count]) if processed_count > 0 else ""
)
if isinstance(dict_or_list, list):
try:
first_key = int(first_key)
except ValueError as e:
message = (
f"`{previous_key}` corresponds to a list, but `{keys[0]}` is not an"
" integer."
)
raise RenderCVUserError(message) from e
if first_key >= len(dict_or_list):
message = (
f"Index {first_key} is out of range for the list `{previous_key}`."
)
raise RenderCVUserError(message)
elif not isinstance(dict_or_list, dict):
message = (
f"It seems like there's something wrong with `{full_key}`, but we don't"
" know what it is."
)
raise RenderCVUserError(message)
if len(keys) == 1:
new_value = value
else:
if isinstance(dict_or_list, dict) and first_key not in dict_or_list:
# Safe: isinstance above guarantees dict, but ty cannot narrow T:
dict_or_list[first_key] = {} # ty: ignore[invalid-assignment]
new_value = update_value_by_location(
# Safe: first_key is validated as int for list, str for dict above:
dict_or_list[first_key], # ty: ignore[invalid-argument-type]
remaining_key,
value,
full_key=full_key,
)
# Safe: first_key type matches dict_or_list indexing per isinstance checks:
dict_or_list[first_key] = new_value # ty: ignore[invalid-assignment]
return dict_or_list
|