@@ -10,3 +10,89 @@ def remove_none_values(d: T) -> T:
10
10
"""Remove all keys with a ``None`` value from a dict."""
11
11
filtered_dict = {k : v for k , v in d .items () if v is not None }
12
12
return cast (T , filtered_dict )
13
+
14
+
15
+ def traverse (d : Any , key_path : str , sep : str = "." , strict : bool = True ) -> Any :
16
+ """
17
+ Retrieve a value from a nested Mapping-like object using a key path.
18
+
19
+ If the object behaves like a Mapping (i.e., implements `__getitem__`),
20
+ this function will be used to access elements.
21
+ If it behaves like an object (i.e., `__getattr__`), the path will be
22
+ resolved as attributes, instead.
23
+
24
+ Parameters
25
+ ----------
26
+ d : dict
27
+ The object to traverse.
28
+ key_path : str
29
+ A string representing the path of keys, separated by `sep`.
30
+ sep : str, optional
31
+ The separator used to split the key path into individual keys.
32
+ Default is ".".
33
+ strict : bool, optional
34
+ If False, return None when a key in the path does not exist.
35
+ If True, raise a KeyError when a key does not exist.
36
+ Default is False.
37
+
38
+ Returns
39
+ -------
40
+ Any
41
+ The value at the specified key path, or None if a key is missing
42
+ and `strict` is False.
43
+
44
+ Raises
45
+ ------
46
+ KeyError
47
+ If `strict` is True and any key in the path does not exist.
48
+
49
+ Examples
50
+ --------
51
+ >>> d = {"foo": {"bar": {"baz": 42}}}
52
+ >>> traverse(d, "foo.bar.baz")
53
+ 42
54
+
55
+ >>> traverse(d, "foo.bar.qux", strict=False)
56
+ None
57
+
58
+ >>> traverse(d, "foo.bar.qux", strict=True)
59
+ Traceback (most recent call last):
60
+ ...
61
+ KeyError: 'qux'
62
+ """
63
+
64
+ def has_item (container , key ):
65
+ # Check if the container is a dictionary or has the __contains__ method
66
+ if hasattr (container , "__contains__" ):
67
+ return key in container
68
+ # Check if it's an object with attributes
69
+ elif hasattr (container , key ):
70
+ return True
71
+ else :
72
+ return False
73
+
74
+ def get_item (container , key , default = None ):
75
+ # Check if the container is a dictionary or supports the `__getitem__` method
76
+ if hasattr (container , "__getitem__" ):
77
+ try :
78
+ return container [key ]
79
+ except (KeyError , IndexError , TypeError ):
80
+ return default
81
+ # Check if it's an object with attributes
82
+ elif hasattr (container , key ):
83
+ return getattr (container , key , default )
84
+ else :
85
+ return default
86
+
87
+ keys = key_path .split (sep )
88
+ for key in keys :
89
+ # Bail out on missing keys in strict mode
90
+ if strict :
91
+ found = has_item (d , key )
92
+ if not found :
93
+ raise KeyError ()
94
+
95
+ d = get_item (d , key )
96
+ if d is None :
97
+ return None
98
+ return d
0 commit comments