aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/deepdiff/path.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/deepdiff/path.py')
-rw-r--r--.venv/lib/python3.12/site-packages/deepdiff/path.py316
1 files changed, 316 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/deepdiff/path.py b/.venv/lib/python3.12/site-packages/deepdiff/path.py
new file mode 100644
index 00000000..ee63b5b9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/deepdiff/path.py
@@ -0,0 +1,316 @@
+import logging
+from ast import literal_eval
+from functools import lru_cache
+
+logger = logging.getLogger(__name__)
+
+GETATTR = 'GETATTR'
+GET = 'GET'
+
+
+class PathExtractionError(ValueError):
+ pass
+
+
+class RootCanNotBeModified(ValueError):
+ pass
+
+
+def _add_to_elements(elements, elem, inside):
+ # Ignore private items
+ if not elem:
+ return
+ if not elem.startswith('__'):
+ remove_quotes = False
+ if '𝆺𝅥𝅯' in elem or '\\' in elem:
+ remove_quotes = True
+ else:
+ try:
+ elem = literal_eval(elem)
+ remove_quotes = False
+ except (ValueError, SyntaxError):
+ remove_quotes = True
+ if remove_quotes and elem[0] == elem[-1] and elem[0] in {'"', "'"}:
+ elem = elem[1: -1]
+ action = GETATTR if inside == '.' else GET
+ elements.append((elem, action))
+
+
+DEFAULT_FIRST_ELEMENT = ('root', GETATTR)
+
+
+@lru_cache(maxsize=1024 * 128)
+def _path_to_elements(path, root_element=DEFAULT_FIRST_ELEMENT):
+ """
+ Given a path, it extracts the elements that form the path and their relevant most likely retrieval action.
+
+ >>> from deepdiff import _path_to_elements
+ >>> path = "root[4.3].b['a3']"
+ >>> _path_to_elements(path, root_element=None)
+ [(4.3, 'GET'), ('b', 'GETATTR'), ('a3', 'GET')]
+ """
+ if isinstance(path, (tuple, list)):
+ return path
+ elements = []
+ if root_element:
+ elements.append(root_element)
+ elem = ''
+ inside = False
+ prev_char = None
+ path = path[4:] # removing "root from the beginning"
+ brackets = []
+ inside_quotes = False
+ quote_used = ''
+ for char in path:
+ if prev_char == '𝆺𝅥𝅯':
+ elem += char
+ elif char in {'"', "'"}:
+ elem += char
+ # If we are inside and the quote is not what we expected, the quote is not closing
+ if not(inside_quotes and quote_used != char):
+ inside_quotes = not inside_quotes
+ if inside_quotes:
+ quote_used = char
+ else:
+ _add_to_elements(elements, elem, inside)
+ elem = ''
+ quote_used = ''
+ elif inside_quotes:
+ elem += char
+ elif char == '[':
+ if inside == '.':
+ _add_to_elements(elements, elem, inside)
+ inside = '['
+ elem = ''
+ # we are already inside. The bracket is a part of the word.
+ elif inside == '[':
+ elem += char
+ else:
+ inside = '['
+ brackets.append('[')
+ elem = ''
+ elif char == '.':
+ if inside == '[':
+ elem += char
+ elif inside == '.':
+ _add_to_elements(elements, elem, inside)
+ elem = ''
+ else:
+ inside = '.'
+ elem = ''
+ elif char == ']':
+ if brackets and brackets[-1] == '[':
+ brackets.pop()
+ if brackets:
+ elem += char
+ else:
+ _add_to_elements(elements, elem, inside)
+ elem = ''
+ inside = False
+ else:
+ elem += char
+ prev_char = char
+ if elem:
+ _add_to_elements(elements, elem, inside)
+ return tuple(elements)
+
+
+def _get_nested_obj(obj, elements, next_element=None):
+ for (elem, action) in elements:
+ if action == GET:
+ obj = obj[elem]
+ elif action == GETATTR:
+ obj = getattr(obj, elem)
+ return obj
+
+
+def _guess_type(elements, elem, index, next_element):
+ # If we are not at the last elements
+ if index < len(elements) - 1:
+ # We assume it is a nested dictionary not a nested list
+ return {}
+ if isinstance(next_element, int):
+ return []
+ return {}
+
+
+def _get_nested_obj_and_force(obj, elements, next_element=None):
+ prev_elem = None
+ prev_action = None
+ prev_obj = obj
+ for index, (elem, action) in enumerate(elements):
+ _prev_obj = obj
+ if action == GET:
+ try:
+ obj = obj[elem]
+ prev_obj = _prev_obj
+ except KeyError:
+ obj[elem] = _guess_type(elements, elem, index, next_element)
+ obj = obj[elem]
+ prev_obj = _prev_obj
+ except IndexError:
+ if isinstance(obj, list) and isinstance(elem, int) and elem >= len(obj):
+ obj.extend([None] * (elem - len(obj)))
+ obj.append(_guess_type(elements, elem, index), next_element)
+ obj = obj[-1]
+ prev_obj = _prev_obj
+ elif isinstance(obj, list) and len(obj) == 0 and prev_elem:
+ # We ran into an empty list that should have been a dictionary
+ # We need to change it from an empty list to a dictionary
+ obj = {elem: _guess_type(elements, elem, index, next_element)}
+ if prev_action == GET:
+ prev_obj[prev_elem] = obj
+ else:
+ setattr(prev_obj, prev_elem, obj)
+ obj = obj[elem]
+ elif action == GETATTR:
+ obj = getattr(obj, elem)
+ prev_obj = _prev_obj
+ prev_elem = elem
+ prev_action = action
+ return obj
+
+
+def extract(obj, path):
+ """
+ Get the item from obj based on path.
+
+ Example:
+
+ >>> from deepdiff import extract
+ >>> obj = {1: [{'2': 'b'}, 3], 2: [4, 5]}
+ >>> path = "root[1][0]['2']"
+ >>> extract(obj, path)
+ 'b'
+
+ Note that you can use extract in conjunction with DeepDiff results
+ or even with the search and :ref:`deepsearch_label` modules. For example:
+
+ >>> from deepdiff import grep
+ >>> obj = {1: [{'2': 'b'}, 3], 2: [4, 5]}
+ >>> result = obj | grep(5)
+ >>> result
+ {'matched_values': ['root[2][1]']}
+ >>> result['matched_values'][0]
+ 'root[2][1]'
+ >>> path = result['matched_values'][0]
+ >>> extract(obj, path)
+ 5
+
+
+ .. note::
+ Note that even if DeepDiff tried gives you a path to an item in a set,
+ there is no such thing in Python and hence you will get an error trying
+ to extract that item from a set.
+ If you want to be able to get items from sets, use the SetOrdered module
+ to generate the sets.
+ In fact Deepdiff uses SetOrdered as a dependency.
+
+ >>> from deepdiff import grep, extract
+ >>> obj = {"a", "b"}
+ >>> obj | grep("b")
+ Set item detected in the path.'set' objects do NOT support indexing. But DeepSearch will still report a path.
+ {'matched_values': SetOrdered(['root[0]'])}
+ >>> extract(obj, 'root[0]')
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ File "deepdiff/deepdiff/path.py", line 126, in extract
+ return _get_nested_obj(obj, elements)
+ File "deepdiff/deepdiff/path.py", line 84, in _get_nested_obj
+ obj = obj[elem]
+ TypeError: 'set' object is not subscriptable
+ >>> from orderly_set import SetOrdered
+ >>> obj = SetOrdered(["a", "b"])
+ >>> extract(obj, 'root[0]')
+ 'a'
+
+ """
+ elements = _path_to_elements(path, root_element=None)
+ return _get_nested_obj(obj, elements)
+
+
+def parse_path(path, root_element=DEFAULT_FIRST_ELEMENT, include_actions=False):
+ """
+ Parse a path to a format that is machine readable
+
+ **Parameters**
+
+ path : A string
+ The path string such as "root[1][2]['age']"
+
+ root_element: string, default='root'
+ What the root is called in the path.
+
+ include_actions: boolean, default=False
+ If True, we return the action required to retrieve the item at each element of the path.
+
+ **Examples**
+
+ >>> from deepdiff import parse_path
+ >>> parse_path("root[1][2]['age']")
+ [1, 2, 'age']
+ >>> parse_path("root[1][2]['age']", include_actions=True)
+ [{'element': 1, 'action': 'GET'}, {'element': 2, 'action': 'GET'}, {'element': 'age', 'action': 'GET'}]
+ >>>
+ >>> parse_path("root['joe'].age")
+ ['joe', 'age']
+ >>> parse_path("root['joe'].age", include_actions=True)
+ [{'element': 'joe', 'action': 'GET'}, {'element': 'age', 'action': 'GETATTR'}]
+
+ """
+
+ result = _path_to_elements(path, root_element=root_element)
+ result = iter(result)
+ if root_element:
+ next(result) # We don't want the root item
+ if include_actions is False:
+ return [i[0] for i in result]
+ return [{'element': i[0], 'action': i[1]} for i in result]
+
+
+def stringify_element(param, quote_str=None):
+ has_quote = "'" in param
+ has_double_quote = '"' in param
+ if has_quote and has_double_quote and not quote_str:
+ new_param = []
+ for char in param:
+ if char in {'"', "'"}:
+ new_param.append('𝆺𝅥𝅯')
+ new_param.append(char)
+ result = '"' + ''.join(new_param) + '"'
+ elif has_quote:
+ result = f'"{param}"'
+ elif has_double_quote:
+ result = f"'{param}'"
+ else:
+ result = param if quote_str is None else quote_str.format(param)
+ return result
+
+
+def stringify_path(path, root_element=DEFAULT_FIRST_ELEMENT, quote_str="'{}'"):
+ """
+ Gets the path as an string.
+
+ For example [1, 2, 'age'] should become
+ root[1][2]['age']
+ """
+ if not path:
+ return root_element[0]
+ result = [root_element[0]]
+ has_actions = False
+ try:
+ if path[0][1] in {GET, GETATTR}:
+ has_actions = True
+ except (KeyError, IndexError, TypeError):
+ pass
+ if not has_actions:
+ path = [(i, GET) for i in path]
+ path[0] = (path[0][0], root_element[1]) # The action for the first element might be a GET or GETATTR. We update the action based on the root_element.
+ for element, action in path:
+ if isinstance(element, str) and action == GET:
+ element = stringify_element(element, quote_str)
+ if action == GET:
+ result.append(f"[{element}]")
+ else:
+ result.append(f".{element}")
+ return ''.join(result)