Source code for behave_kit.utils
"""`get_path` — navigate nested dicts with dot notation.
Avoids chaining ``[]`` and handling ``KeyError`` / ``TypeError`` manually
when extracting a value from a deeply nested JSON/dict response.
"""
from __future__ import annotations
from typing import Any
from behave_kit._core.errors import BehaveKitError
_MISSING = object()
[docs]
def get_path(data: object, path: str, default: Any = _MISSING) -> Any:
"""Return the value at ``path`` within nested ``data``, or ``default``.
``path`` uses dot notation to traverse nested dicts:
get_path(response, "user.address.city")
Numeric segments traverse lists:
get_path(response, "users.0.name")
Args:
data: The root dict or list to traverse.
path: Dot-separated path string.
default: Value to return when the path does not exist.
If not provided, `BehaveKitError` is raised instead.
Raises:
BehaveKitError: When the path is not found and no ``default`` is given.
"""
if not path:
raise BehaveKitError(
"path must not be empty",
suggestion='Use a dot-separated path like "user.address.city"',
)
current: object = data
for segment in path.split("."):
if segment == "":
raise BehaveKitError(
f"Invalid path '{path}': empty segment",
suggestion="Remove consecutive dots from the path",
)
if isinstance(current, dict):
if segment not in current:
if default is not _MISSING:
return default
raise BehaveKitError(
f"Key '{segment}' not found in path '{path}'",
suggestion=f"Available keys: {', '.join(sorted(current)) or '(none)'}",
)
current = current[segment]
elif isinstance(current, list):
try:
index = int(segment)
except ValueError as exc:
if default is not _MISSING:
return default
raise BehaveKitError(
f"Cannot index list with '{segment}' in path '{path}'",
cause=exc,
suggestion="Use a numeric index for list segments",
) from exc
if index < 0 or index >= len(current):
if default is not _MISSING:
return default
raise BehaveKitError(
f"Index {index} out of range for list of length {len(current)} "
f"in path '{path}'",
suggestion=(
f"Valid indices: 0-{len(current) - 1}" if current else "List is empty"
),
)
current = current[index]
else:
if default is not _MISSING:
return default
raise BehaveKitError(
f"Cannot traverse into {type(current).__name__} at '{segment}' in path '{path}'",
suggestion="Check the path matches the data structure",
)
return current