lionagi 0.16.1__py3-none-any.whl → 0.16.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lionagi/adapters/_utils.py +0 -14
- lionagi/libs/file/save.py +8 -1
- lionagi/ln/__init__.py +10 -0
- lionagi/ln/_json_dump.py +322 -49
- lionagi/ln/fuzzy/__init__.py +4 -1
- lionagi/ln/fuzzy/_fuzzy_validate.py +109 -0
- lionagi/ln/fuzzy/_to_dict.py +388 -0
- lionagi/models/__init__.py +0 -2
- lionagi/operations/brainstorm/brainstorm.py +10 -10
- lionagi/operations/communicate/communicate.py +1 -1
- lionagi/operations/parse/parse.py +1 -1
- lionagi/protocols/generic/element.py +5 -14
- lionagi/protocols/generic/log.py +2 -2
- lionagi/protocols/generic/pile.py +1 -1
- lionagi/protocols/messages/message.py +8 -1
- lionagi/protocols/operatives/operative.py +2 -2
- lionagi/service/connections/endpoint.py +7 -0
- lionagi/service/connections/match_endpoint.py +2 -10
- lionagi/service/connections/providers/types.py +1 -3
- lionagi/service/hooks/hook_event.py +1 -1
- lionagi/service/hooks/hook_registry.py +1 -1
- lionagi/service/rate_limited_processor.py +1 -1
- lionagi/session/branch.py +1 -101
- lionagi/session/session.py +9 -14
- lionagi/utils.py +3 -334
- lionagi/version.py +1 -1
- {lionagi-0.16.1.dist-info → lionagi-0.16.3.dist-info}/METADATA +3 -13
- {lionagi-0.16.1.dist-info → lionagi-0.16.3.dist-info}/RECORD +30 -78
- lionagi/adapters/postgres_model_adapter.py +0 -131
- lionagi/libs/concurrency.py +0 -1
- lionagi/libs/file/params.py +0 -175
- lionagi/libs/nested/__init__.py +0 -3
- lionagi/libs/nested/flatten.py +0 -172
- lionagi/libs/nested/nfilter.py +0 -59
- lionagi/libs/nested/nget.py +0 -45
- lionagi/libs/nested/ninsert.py +0 -104
- lionagi/libs/nested/nmerge.py +0 -158
- lionagi/libs/nested/npop.py +0 -69
- lionagi/libs/nested/nset.py +0 -94
- lionagi/libs/nested/unflatten.py +0 -83
- lionagi/libs/nested/utils.py +0 -189
- lionagi/libs/parse.py +0 -31
- lionagi/libs/schema/json_schema.py +0 -231
- lionagi/libs/token_transform/__init__.py +0 -0
- lionagi/libs/token_transform/base.py +0 -54
- lionagi/libs/token_transform/llmlingua.py +0 -1
- lionagi/libs/token_transform/perplexity.py +0 -450
- lionagi/libs/token_transform/symbolic_compress_context.py +0 -152
- lionagi/libs/token_transform/synthlang.py +0 -9
- lionagi/libs/token_transform/synthlang_/base.py +0 -128
- lionagi/libs/token_transform/synthlang_/resources/frameworks/abstract_algebra.toml +0 -11
- lionagi/libs/token_transform/synthlang_/resources/frameworks/category_theory.toml +0 -11
- lionagi/libs/token_transform/synthlang_/resources/frameworks/complex_analysis.toml +0 -11
- lionagi/libs/token_transform/synthlang_/resources/frameworks/framework_options.json +0 -52
- lionagi/libs/token_transform/synthlang_/resources/frameworks/group_theory.toml +0 -11
- lionagi/libs/token_transform/synthlang_/resources/frameworks/math_logic.toml +0 -11
- lionagi/libs/token_transform/synthlang_/resources/frameworks/reflective_patterns.toml +0 -11
- lionagi/libs/token_transform/synthlang_/resources/frameworks/set_theory.toml +0 -11
- lionagi/libs/token_transform/synthlang_/resources/frameworks/topology_fundamentals.toml +0 -11
- lionagi/libs/token_transform/synthlang_/resources/mapping/lion_emoji_mapping.toml +0 -61
- lionagi/libs/token_transform/synthlang_/resources/mapping/python_math_mapping.toml +0 -41
- lionagi/libs/token_transform/synthlang_/resources/mapping/rust_chinese_mapping.toml +0 -60
- lionagi/libs/token_transform/synthlang_/resources/utility/base_synthlang_system_prompt.toml +0 -11
- lionagi/libs/token_transform/synthlang_/translate_to_synthlang.py +0 -140
- lionagi/libs/token_transform/types.py +0 -15
- lionagi/libs/unstructured/__init__.py +0 -0
- lionagi/libs/unstructured/pdf_to_image.py +0 -45
- lionagi/libs/unstructured/read_image_to_base64.py +0 -33
- lionagi/libs/validate/fuzzy_match_keys.py +0 -7
- lionagi/libs/validate/fuzzy_validate_mapping.py +0 -144
- lionagi/libs/validate/string_similarity.py +0 -7
- lionagi/libs/validate/xml_parser.py +0 -203
- lionagi/models/note.py +0 -383
- lionagi/operations/translate/__init__.py +0 -0
- lionagi/operations/translate/translate.py +0 -47
- lionagi/service/connections/providers/claude_code_.py +0 -294
- lionagi/tools/memory/tools.py +0 -495
- {lionagi-0.16.1.dist-info → lionagi-0.16.3.dist-info}/WHEEL +0 -0
- {lionagi-0.16.1.dist-info → lionagi-0.16.3.dist-info}/licenses/LICENSE +0 -0
lionagi/libs/nested/flatten.py
DELETED
@@ -1,172 +0,0 @@
|
|
1
|
-
# Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
from collections import deque
|
6
|
-
from collections.abc import Mapping, Sequence
|
7
|
-
from typing import Any, Literal, TypeVar, overload
|
8
|
-
|
9
|
-
T = TypeVar("T")
|
10
|
-
|
11
|
-
|
12
|
-
@overload
|
13
|
-
def flatten(
|
14
|
-
nested_structure: T,
|
15
|
-
/,
|
16
|
-
*,
|
17
|
-
parent_key: tuple = (),
|
18
|
-
sep: str = "|",
|
19
|
-
coerce_keys: Literal[True] = True,
|
20
|
-
dynamic: bool = True,
|
21
|
-
coerce_sequence: Literal["dict", None] = None,
|
22
|
-
max_depth: int | None = None,
|
23
|
-
) -> dict[str, Any] | None: ...
|
24
|
-
|
25
|
-
|
26
|
-
@overload
|
27
|
-
def flatten(
|
28
|
-
nested_structure: T,
|
29
|
-
/,
|
30
|
-
*,
|
31
|
-
parent_key: tuple = (),
|
32
|
-
sep: str = "|",
|
33
|
-
coerce_keys: Literal[False],
|
34
|
-
dynamic: bool = True,
|
35
|
-
coerce_sequence: Literal["dict", "list", None] = None,
|
36
|
-
max_depth: int | None = None,
|
37
|
-
) -> dict[tuple, Any] | None: ...
|
38
|
-
|
39
|
-
|
40
|
-
def flatten(
|
41
|
-
nested_structure: Any,
|
42
|
-
/,
|
43
|
-
*,
|
44
|
-
parent_key: tuple = (),
|
45
|
-
sep: str = "|",
|
46
|
-
coerce_keys: bool = True,
|
47
|
-
dynamic: bool = True,
|
48
|
-
coerce_sequence: Literal["dict", "list"] | None = None,
|
49
|
-
max_depth: int | None = None,
|
50
|
-
) -> dict[tuple | str, Any] | None:
|
51
|
-
"""Flatten a nested structure into a single-level dictionary.
|
52
|
-
|
53
|
-
Recursively traverses the input, creating keys that represent the path
|
54
|
-
to each value in the flattened result.
|
55
|
-
|
56
|
-
Args:
|
57
|
-
nested_structure: The nested structure to flatten.
|
58
|
-
parent_key: Base key for the current recursion level. Default: ().
|
59
|
-
sep: Separator for joining keys. Default: "|".
|
60
|
-
coerce_keys: Join keys into strings if True, keep as tuples if False.
|
61
|
-
Default: True.
|
62
|
-
dynamic: Handle sequences (except strings) dynamically if True.
|
63
|
-
Default: True.
|
64
|
-
coerce_sequence: Force sequences to be treated as dicts or lists.
|
65
|
-
Options: "dict", "list", or None. Default: None.
|
66
|
-
max_depth: Maximum depth to flatten. None for complete flattening.
|
67
|
-
Default: None.
|
68
|
-
|
69
|
-
Returns:
|
70
|
-
A flattened dictionary with keys as tuples or strings (based on
|
71
|
-
coerce_keys) representing the path to each value.
|
72
|
-
|
73
|
-
Raises:
|
74
|
-
ValueError: If coerce_sequence is "list" and coerce_keys is True.
|
75
|
-
|
76
|
-
Example:
|
77
|
-
>>> nested = {"a": 1, "b": {"c": 2, "d": [3, 4]}}
|
78
|
-
>>> flatten(nested)
|
79
|
-
{'a': 1, 'b|c': 2, 'b|d|0': 3, 'b|d|1': 4}
|
80
|
-
|
81
|
-
Note:
|
82
|
-
- Preserves order of keys in dicts and indices in sequences.
|
83
|
-
- With dynamic=True, treats sequences (except strings) as nestable.
|
84
|
-
- coerce_sequence allows forcing sequence handling for homogeneity.
|
85
|
-
"""
|
86
|
-
|
87
|
-
if coerce_keys and coerce_sequence == "list":
|
88
|
-
raise ValueError(
|
89
|
-
"coerce_sequence cannot be 'list' when coerce_keys is True"
|
90
|
-
)
|
91
|
-
|
92
|
-
coerce_sequence_to_list = None
|
93
|
-
coerce_sequence_to_dict = None
|
94
|
-
|
95
|
-
if dynamic and coerce_sequence:
|
96
|
-
if coerce_sequence == "dict":
|
97
|
-
coerce_sequence_to_dict = True
|
98
|
-
elif coerce_sequence == "list":
|
99
|
-
coerce_sequence_to_list = True
|
100
|
-
|
101
|
-
return _flatten_iterative(
|
102
|
-
obj=nested_structure,
|
103
|
-
parent_key=parent_key,
|
104
|
-
sep=sep,
|
105
|
-
coerce_keys=coerce_keys,
|
106
|
-
dynamic=dynamic,
|
107
|
-
coerce_sequence_to_list=coerce_sequence_to_list,
|
108
|
-
coerce_sequence_to_dict=coerce_sequence_to_dict,
|
109
|
-
max_depth=max_depth,
|
110
|
-
)
|
111
|
-
|
112
|
-
|
113
|
-
def _flatten_iterative(
|
114
|
-
obj: Any,
|
115
|
-
parent_key: tuple,
|
116
|
-
sep: str,
|
117
|
-
coerce_keys: bool,
|
118
|
-
dynamic: bool,
|
119
|
-
coerce_sequence_to_list: bool = False,
|
120
|
-
coerce_sequence_to_dict: bool = False,
|
121
|
-
max_depth: int | None = None,
|
122
|
-
) -> dict[tuple | str, Any]:
|
123
|
-
stack = deque([(obj, parent_key, 0)])
|
124
|
-
result = {}
|
125
|
-
|
126
|
-
while stack:
|
127
|
-
current_obj, current_key, depth = stack.pop()
|
128
|
-
|
129
|
-
if max_depth is not None and depth >= max_depth:
|
130
|
-
result[_format_key(current_key, sep, coerce_keys)] = current_obj
|
131
|
-
continue
|
132
|
-
|
133
|
-
if isinstance(current_obj, Mapping):
|
134
|
-
for k, v in current_obj.items():
|
135
|
-
new_key = current_key + (k,)
|
136
|
-
if (
|
137
|
-
v
|
138
|
-
and isinstance(v, (Mapping, Sequence))
|
139
|
-
and not isinstance(v, (str, bytes, bytearray))
|
140
|
-
):
|
141
|
-
stack.appendleft((v, new_key, depth + 1))
|
142
|
-
else:
|
143
|
-
result[_format_key(new_key, sep, coerce_keys)] = v
|
144
|
-
|
145
|
-
elif (
|
146
|
-
dynamic
|
147
|
-
and isinstance(current_obj, Sequence)
|
148
|
-
and not isinstance(current_obj, (str, bytes, bytearray))
|
149
|
-
):
|
150
|
-
if coerce_sequence_to_dict:
|
151
|
-
dict_obj = {str(i): v for i, v in enumerate(current_obj)}
|
152
|
-
for k, v in dict_obj.items():
|
153
|
-
new_key = current_key + (k,)
|
154
|
-
stack.appendleft((v, new_key, depth + 1))
|
155
|
-
elif coerce_sequence_to_list:
|
156
|
-
for i, v in enumerate(current_obj):
|
157
|
-
new_key = current_key + (i,)
|
158
|
-
stack.appendleft((v, new_key, depth + 1))
|
159
|
-
else:
|
160
|
-
for i, v in enumerate(current_obj):
|
161
|
-
new_key = current_key + (str(i),)
|
162
|
-
stack.appendleft((v, new_key, depth + 1))
|
163
|
-
else:
|
164
|
-
result[_format_key(current_key, sep, coerce_keys)] = current_obj
|
165
|
-
|
166
|
-
return result
|
167
|
-
|
168
|
-
|
169
|
-
def _format_key(key: tuple, sep: str, coerce_keys: bool, /) -> tuple | str:
|
170
|
-
if not key:
|
171
|
-
return key
|
172
|
-
return sep.join(map(str, key)) if coerce_keys else key
|
lionagi/libs/nested/nfilter.py
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
# Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
from collections.abc import Callable
|
6
|
-
from typing import Any
|
7
|
-
|
8
|
-
|
9
|
-
def nfilter(
|
10
|
-
nested_structure: dict[Any, Any] | list[Any],
|
11
|
-
/,
|
12
|
-
condition: Callable[[Any], bool],
|
13
|
-
) -> dict[Any, Any] | list[Any]:
|
14
|
-
"""Filter elements in a nested structure based on a condition.
|
15
|
-
|
16
|
-
Args:
|
17
|
-
nested_structure: The nested structure (dict or list) to filter.
|
18
|
-
condition: Function returning True for elements to keep, False to
|
19
|
-
discard.
|
20
|
-
|
21
|
-
Returns:
|
22
|
-
The filtered nested structure.
|
23
|
-
|
24
|
-
Raises:
|
25
|
-
TypeError: If nested_structure is not a dict or list.
|
26
|
-
|
27
|
-
Example:
|
28
|
-
>>> data = {"a": 1, "b": {"c": 2, "d": 3}, "e": [4, 5, 6]}
|
29
|
-
>>> nfilter(data, lambda x: isinstance(x, int) and x > 2)
|
30
|
-
{'b': {'d': 3}, 'e': [4, 5, 6]}
|
31
|
-
"""
|
32
|
-
if isinstance(nested_structure, dict):
|
33
|
-
return _filter_dict(nested_structure, condition)
|
34
|
-
elif isinstance(nested_structure, list):
|
35
|
-
return _filter_list(nested_structure, condition)
|
36
|
-
else:
|
37
|
-
raise TypeError(
|
38
|
-
"The nested_structure must be either a dict or a list."
|
39
|
-
)
|
40
|
-
|
41
|
-
|
42
|
-
def _filter_dict(
|
43
|
-
dictionary: dict[Any, Any], condition: Callable[[tuple[Any, Any]], bool]
|
44
|
-
) -> dict[Any, Any]:
|
45
|
-
return {
|
46
|
-
k: nfilter(v, condition) if isinstance(v, dict | list) else v
|
47
|
-
for k, v in dictionary.items()
|
48
|
-
if condition(v) or isinstance(v, dict | list)
|
49
|
-
}
|
50
|
-
|
51
|
-
|
52
|
-
def _filter_list(
|
53
|
-
lst: list[Any], condition: Callable[[Any], bool]
|
54
|
-
) -> list[Any]:
|
55
|
-
return [
|
56
|
-
nfilter(item, condition) if isinstance(item, dict | list) else item
|
57
|
-
for item in lst
|
58
|
-
if condition(item) or isinstance(item, dict | list)
|
59
|
-
]
|
lionagi/libs/nested/nget.py
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
# Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
from typing import Any
|
6
|
-
|
7
|
-
from lionagi.utils import UNDEFINED
|
8
|
-
|
9
|
-
from .utils import get_target_container
|
10
|
-
|
11
|
-
|
12
|
-
def nget(
|
13
|
-
nested_structure: dict[Any, Any] | list[Any],
|
14
|
-
/,
|
15
|
-
indices: list[int | str],
|
16
|
-
default: Any = UNDEFINED,
|
17
|
-
) -> Any:
|
18
|
-
try:
|
19
|
-
target_container = get_target_container(nested_structure, indices[:-1])
|
20
|
-
last_index = indices[-1]
|
21
|
-
|
22
|
-
if (
|
23
|
-
isinstance(target_container, list)
|
24
|
-
and isinstance(last_index, int)
|
25
|
-
and last_index < len(target_container)
|
26
|
-
):
|
27
|
-
return target_container[last_index]
|
28
|
-
elif (
|
29
|
-
isinstance(target_container, dict)
|
30
|
-
and last_index in target_container
|
31
|
-
):
|
32
|
-
return target_container[last_index]
|
33
|
-
elif default is not UNDEFINED:
|
34
|
-
return default
|
35
|
-
else:
|
36
|
-
raise LookupError(
|
37
|
-
"Target not found and no default value provided."
|
38
|
-
)
|
39
|
-
except (IndexError, KeyError, TypeError):
|
40
|
-
if default is not UNDEFINED:
|
41
|
-
return default
|
42
|
-
else:
|
43
|
-
raise LookupError(
|
44
|
-
"Target not found and no default value provided."
|
45
|
-
)
|
lionagi/libs/nested/ninsert.py
DELETED
@@ -1,104 +0,0 @@
|
|
1
|
-
# Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
from typing import Any
|
6
|
-
|
7
|
-
from lionagi.utils import to_list
|
8
|
-
|
9
|
-
|
10
|
-
def ninsert(
|
11
|
-
nested_structure: dict[Any, Any] | list[Any],
|
12
|
-
/,
|
13
|
-
indices: list[str | int],
|
14
|
-
value: Any,
|
15
|
-
*,
|
16
|
-
current_depth: int = 0,
|
17
|
-
) -> None:
|
18
|
-
"""
|
19
|
-
Inserts a value into a nested structure at a specified path.
|
20
|
-
|
21
|
-
Navigates a nested dictionary or list based on a sequence of indices or
|
22
|
-
keys and inserts `value` at the final location. This method can create
|
23
|
-
intermediate dictionaries or lists as needed.
|
24
|
-
|
25
|
-
Args:
|
26
|
-
nested_structure: The nested structure to modify.
|
27
|
-
indices: The sequence of keys or indices defining the insertion path.
|
28
|
-
value: The value to insert at the specified location.
|
29
|
-
current_depth: Internal use only; tracks the current depth during
|
30
|
-
recursive calls.
|
31
|
-
|
32
|
-
Raises:
|
33
|
-
ValueError: If the indices list is empty.
|
34
|
-
TypeError: If an invalid key or container type is encountered.
|
35
|
-
|
36
|
-
Examples:
|
37
|
-
>>> subject_ = {'a': {'b': [1, 2]}}
|
38
|
-
>>> ninsert(subject_, ['a', 'b', 2], 3)
|
39
|
-
>>> assert subject_ == {'a': {'b': [1, 2, 3]}}
|
40
|
-
|
41
|
-
>>> subject_ = []
|
42
|
-
>>> ninsert(subject_, [0, 'a'], 1)
|
43
|
-
>>> assert subject_ == [{'a': 1}]
|
44
|
-
"""
|
45
|
-
if not indices:
|
46
|
-
raise ValueError("Indices list cannot be empty")
|
47
|
-
|
48
|
-
indices = to_list(indices)
|
49
|
-
for i, part in enumerate(indices[:-1]):
|
50
|
-
if isinstance(part, int):
|
51
|
-
if isinstance(nested_structure, dict):
|
52
|
-
raise TypeError(
|
53
|
-
f"Unsupported key type: {type(part).__name__}.Only string keys are acceptable.",
|
54
|
-
)
|
55
|
-
while len(nested_structure) <= part:
|
56
|
-
nested_structure.append(None)
|
57
|
-
if nested_structure[part] is None or not isinstance(
|
58
|
-
nested_structure[part], (dict, list)
|
59
|
-
):
|
60
|
-
next_part = indices[i + 1]
|
61
|
-
nested_structure[part] = (
|
62
|
-
[] if isinstance(next_part, int) else {}
|
63
|
-
)
|
64
|
-
elif isinstance(nested_structure, dict):
|
65
|
-
if part is None:
|
66
|
-
raise TypeError("Cannot use NoneType as a key in a dictionary")
|
67
|
-
if isinstance(part, (float, complex)):
|
68
|
-
raise TypeError(
|
69
|
-
f"Unsupported key type: {type(part).__name__}.Only string keys are acceptable.",
|
70
|
-
)
|
71
|
-
if part not in nested_structure:
|
72
|
-
next_part = indices[i + 1]
|
73
|
-
nested_structure[part] = (
|
74
|
-
[] if isinstance(next_part, int) else {}
|
75
|
-
)
|
76
|
-
else:
|
77
|
-
raise TypeError(
|
78
|
-
f"Invalid container type: {type(nested_structure)} encountered during insertion"
|
79
|
-
)
|
80
|
-
|
81
|
-
nested_structure = nested_structure[part]
|
82
|
-
current_depth += 1
|
83
|
-
|
84
|
-
last_part = indices[-1]
|
85
|
-
if isinstance(last_part, int):
|
86
|
-
if isinstance(nested_structure, dict):
|
87
|
-
raise TypeError(
|
88
|
-
f"Unsupported key type: {type(last_part).__name__}."
|
89
|
-
"Only string keys are acceptable.",
|
90
|
-
)
|
91
|
-
while len(nested_structure) <= last_part:
|
92
|
-
nested_structure.append(None)
|
93
|
-
nested_structure[last_part] = value
|
94
|
-
elif isinstance(nested_structure, list):
|
95
|
-
raise TypeError("Cannot use non-integer index on a list")
|
96
|
-
else:
|
97
|
-
if last_part is None:
|
98
|
-
raise TypeError("Cannot use NoneType as a key in a dictionary")
|
99
|
-
if isinstance(last_part, (float, complex)):
|
100
|
-
raise TypeError(
|
101
|
-
f"Unsupported key type: {type(last_part).__name__}."
|
102
|
-
"Only string keys are acceptable.",
|
103
|
-
)
|
104
|
-
nested_structure[last_part] = value
|
lionagi/libs/nested/nmerge.py
DELETED
@@ -1,158 +0,0 @@
|
|
1
|
-
# Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
from collections import defaultdict
|
6
|
-
from collections.abc import Callable, Sequence
|
7
|
-
from itertools import chain
|
8
|
-
from typing import Any
|
9
|
-
|
10
|
-
from .utils import is_homogeneous
|
11
|
-
|
12
|
-
|
13
|
-
def nmerge(
|
14
|
-
nested_structure: Sequence[dict[str, Any] | list[Any]],
|
15
|
-
/,
|
16
|
-
*,
|
17
|
-
overwrite: bool = False,
|
18
|
-
dict_sequence: bool = False,
|
19
|
-
sort_list: bool = False,
|
20
|
-
custom_sort: Callable[[Any], Any] | None = None,
|
21
|
-
) -> dict[str, Any] | list[Any]:
|
22
|
-
"""
|
23
|
-
Merge multiple dictionaries, lists, or sequences into a unified structure.
|
24
|
-
|
25
|
-
Args:
|
26
|
-
nested_structure: A sequence containing dictionaries, lists, or other
|
27
|
-
iterable objects to merge.
|
28
|
-
overwrite: If True, overwrite existing keys in dictionaries with
|
29
|
-
those from subsequent dictionaries.
|
30
|
-
dict_sequence: Enables unique key generation for duplicate keys by
|
31
|
-
appending a sequence number. Applicable only if `overwrite` is
|
32
|
-
False.
|
33
|
-
sort_list: When True, sort the resulting list after merging. It does
|
34
|
-
not affect dictionaries.
|
35
|
-
custom_sort: An optional callable that defines custom sorting logic
|
36
|
-
for the merged list.
|
37
|
-
|
38
|
-
Returns:
|
39
|
-
A merged dictionary or list, depending on the types present in
|
40
|
-
`nested_structure`.
|
41
|
-
|
42
|
-
Raises:
|
43
|
-
TypeError: If `nested_structure` contains objects of incompatible
|
44
|
-
types that cannot be merged.
|
45
|
-
"""
|
46
|
-
if not isinstance(nested_structure, list):
|
47
|
-
raise TypeError("Please input a list")
|
48
|
-
if is_homogeneous(nested_structure, dict):
|
49
|
-
return _merge_dicts(nested_structure, overwrite, dict_sequence)
|
50
|
-
elif is_homogeneous(nested_structure, list):
|
51
|
-
return _merge_sequences(nested_structure, sort_list, custom_sort)
|
52
|
-
else:
|
53
|
-
raise TypeError(
|
54
|
-
"All items in the input list must be of the same type, either dict, list, or Iterable."
|
55
|
-
)
|
56
|
-
|
57
|
-
|
58
|
-
def _deep_merge_dicts(
|
59
|
-
dict1: dict[str, Any], dict2: dict[str, Any]
|
60
|
-
) -> dict[str, Any]:
|
61
|
-
"""
|
62
|
-
Recursively merges two dictionaries, combining values where keys overlap.
|
63
|
-
|
64
|
-
Args:
|
65
|
-
dict1: The first dictionary.
|
66
|
-
dict2: The second dictionary.
|
67
|
-
|
68
|
-
Returns:
|
69
|
-
The merged dictionary.
|
70
|
-
"""
|
71
|
-
for key in dict2:
|
72
|
-
if key in dict1:
|
73
|
-
if isinstance(dict1[key], dict) and isinstance(dict2[key], dict):
|
74
|
-
_deep_merge_dicts(dict1[key], dict2[key])
|
75
|
-
else:
|
76
|
-
if not isinstance(dict1[key], list):
|
77
|
-
dict1[key] = [dict1[key]]
|
78
|
-
dict1[key].append(dict2[key])
|
79
|
-
else:
|
80
|
-
dict1[key] = dict2[key]
|
81
|
-
return dict1
|
82
|
-
|
83
|
-
|
84
|
-
def _merge_dicts(
|
85
|
-
iterables: list[dict[str, Any]],
|
86
|
-
dict_update: bool,
|
87
|
-
dict_sequence: bool,
|
88
|
-
) -> dict[str, Any]:
|
89
|
-
"""
|
90
|
-
Merges a list of dictionaries into a single dictionary, with options for
|
91
|
-
handling duplicate keys and sequences.
|
92
|
-
|
93
|
-
Args:
|
94
|
-
iterables: A list of dictionaries to merge.
|
95
|
-
dict_update: If True, overwrite existing keys in dictionaries
|
96
|
-
with those from subsequent dictionaries.
|
97
|
-
dict_sequence: Enables unique key generation for duplicate keys
|
98
|
-
by appending a sequence number
|
99
|
-
|
100
|
-
Returns:
|
101
|
-
The merged dictionary.
|
102
|
-
"""
|
103
|
-
merged_dict = {} # {'a': [1, 2]}
|
104
|
-
sequence_counters = defaultdict(int)
|
105
|
-
list_values = {}
|
106
|
-
|
107
|
-
for d in iterables: # [{'a': [1, 2]}, {'a': [3, 4]}]
|
108
|
-
for key, value in d.items(): # {'a': [3, 4]}
|
109
|
-
if key not in merged_dict or dict_update:
|
110
|
-
if (
|
111
|
-
key in merged_dict
|
112
|
-
and isinstance(merged_dict[key], dict)
|
113
|
-
and isinstance(value, dict)
|
114
|
-
):
|
115
|
-
_deep_merge_dicts(merged_dict[key], value)
|
116
|
-
else:
|
117
|
-
merged_dict[key] = value # {'a': [1, 2]}
|
118
|
-
if isinstance(value, list):
|
119
|
-
list_values[key] = True
|
120
|
-
elif dict_sequence:
|
121
|
-
sequence_counters[key] += 1
|
122
|
-
new_key = f"{key}{sequence_counters[key]}"
|
123
|
-
merged_dict[new_key] = value
|
124
|
-
else:
|
125
|
-
if not isinstance(merged_dict[key], list) or list_values.get(
|
126
|
-
key, False
|
127
|
-
):
|
128
|
-
merged_dict[key] = [merged_dict[key]]
|
129
|
-
merged_dict[key].append(value)
|
130
|
-
|
131
|
-
return merged_dict
|
132
|
-
|
133
|
-
|
134
|
-
def _merge_sequences(
|
135
|
-
iterables: list[list[Any]],
|
136
|
-
sort_list: bool,
|
137
|
-
custom_sort: Callable[[Any], Any] | None = None,
|
138
|
-
) -> list[Any]:
|
139
|
-
"""
|
140
|
-
Merges a list of lists into a single list, with options for sorting and
|
141
|
-
custom sorting logic.
|
142
|
-
|
143
|
-
Args:
|
144
|
-
iterables: A list of lists to merge.
|
145
|
-
sort_list: When True, sort the resulting list after merging.
|
146
|
-
custom_sort: An optional callable that defines custom sorting logic
|
147
|
-
for the merged list.
|
148
|
-
|
149
|
-
Returns:
|
150
|
-
The merged list.
|
151
|
-
"""
|
152
|
-
merged_list = list(chain(*iterables))
|
153
|
-
if sort_list:
|
154
|
-
if custom_sort:
|
155
|
-
return sorted(merged_list, key=custom_sort)
|
156
|
-
else:
|
157
|
-
return sorted(merged_list, key=lambda x: (isinstance(x, str), x))
|
158
|
-
return merged_list
|
lionagi/libs/nested/npop.py
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
# Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
from collections.abc import Sequence
|
6
|
-
from typing import Any
|
7
|
-
|
8
|
-
from lionagi.utils import UNDEFINED, to_list
|
9
|
-
|
10
|
-
|
11
|
-
def npop(
|
12
|
-
input_: dict[str, Any] | list[Any],
|
13
|
-
/,
|
14
|
-
indices: str | int | Sequence[str | int],
|
15
|
-
default: Any = UNDEFINED,
|
16
|
-
) -> Any:
|
17
|
-
"""
|
18
|
-
Perform a nested pop operation on the input structure.
|
19
|
-
|
20
|
-
This function navigates through the nested structure using the provided
|
21
|
-
indices and removes and returns the value at the final location.
|
22
|
-
|
23
|
-
Args:
|
24
|
-
input_: The input nested structure (dict or list) to pop from.
|
25
|
-
indices: A single index or a sequence of indices to navigate the
|
26
|
-
nested structure.
|
27
|
-
default: The value to return if the key is not found. If not
|
28
|
-
provided, a KeyError will be raised.
|
29
|
-
|
30
|
-
Returns:
|
31
|
-
The value at the specified nested location.
|
32
|
-
|
33
|
-
Raises:
|
34
|
-
ValueError: If the indices list is empty.
|
35
|
-
KeyError: If a key is not found in a dictionary.
|
36
|
-
IndexError: If an index is out of range for a list.
|
37
|
-
TypeError: If an operation is not supported on the current data type.
|
38
|
-
"""
|
39
|
-
if not indices:
|
40
|
-
raise ValueError("Indices list cannot be empty")
|
41
|
-
|
42
|
-
indices = to_list(indices)
|
43
|
-
|
44
|
-
current = input_
|
45
|
-
for key in indices[:-1]:
|
46
|
-
if isinstance(current, dict):
|
47
|
-
if current.get(key):
|
48
|
-
current = current[key]
|
49
|
-
else:
|
50
|
-
raise KeyError(f"{key} is not found in {current}")
|
51
|
-
elif isinstance(current, list) and isinstance(key, int):
|
52
|
-
if key >= len(current):
|
53
|
-
raise KeyError(
|
54
|
-
f"{key} exceeds the length of the list {current}"
|
55
|
-
)
|
56
|
-
elif key < 0:
|
57
|
-
raise ValueError("list index cannot be negative")
|
58
|
-
current = current[key]
|
59
|
-
|
60
|
-
last_key = indices[-1]
|
61
|
-
try:
|
62
|
-
return current.pop(
|
63
|
-
last_key,
|
64
|
-
)
|
65
|
-
except Exception as e:
|
66
|
-
if default is not UNDEFINED:
|
67
|
-
return default
|
68
|
-
else:
|
69
|
-
raise KeyError(f"Invalid npop. Error: {e}")
|
lionagi/libs/nested/nset.py
DELETED
@@ -1,94 +0,0 @@
|
|
1
|
-
# Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
from collections.abc import Sequence
|
6
|
-
from typing import Any
|
7
|
-
|
8
|
-
from lionagi.utils import to_list
|
9
|
-
|
10
|
-
from .utils import ensure_list_index
|
11
|
-
|
12
|
-
|
13
|
-
def nset(
|
14
|
-
nested_structure: dict[str, Any] | list[Any],
|
15
|
-
/,
|
16
|
-
indices: str | int | Sequence[str | int],
|
17
|
-
value: Any,
|
18
|
-
) -> None:
|
19
|
-
"""Set a value within a nested structure at the specified path.
|
20
|
-
|
21
|
-
This method allows setting a value deep within a nested dictionary or list
|
22
|
-
by specifying a path to the target location using a sequence of indices.
|
23
|
-
Each index in the sequence represents a level in the nested structure,
|
24
|
-
with integers used for list indices and strings for dictionary keys.
|
25
|
-
|
26
|
-
Args:
|
27
|
-
nested_structure: The nested structure to modify.
|
28
|
-
indices: The path of indices leading to the target location.
|
29
|
-
value: The value to set at the specified location.
|
30
|
-
|
31
|
-
Raises:
|
32
|
-
ValueError: If the indices sequence is empty.
|
33
|
-
TypeError: If the target container is not a list or dictionary,
|
34
|
-
or if the index type is incorrect.
|
35
|
-
|
36
|
-
Examples:
|
37
|
-
>>> data = {'a': {'b': [10, 20]}}
|
38
|
-
>>> nset(data, ['a', 'b', 1], 99)
|
39
|
-
>>> assert data == {'a': {'b': [10, 99]}}
|
40
|
-
|
41
|
-
>>> data = [0, [1, 2], 3]
|
42
|
-
>>> nset(data, [1, 1], 99)
|
43
|
-
>>> assert data == [0, [1, 99], 3]
|
44
|
-
"""
|
45
|
-
|
46
|
-
if not indices:
|
47
|
-
raise ValueError(
|
48
|
-
"Indices list is empty, cannot determine target container"
|
49
|
-
)
|
50
|
-
|
51
|
-
_indices = to_list(indices)
|
52
|
-
target_container = nested_structure
|
53
|
-
|
54
|
-
for i, index in enumerate(_indices[:-1]):
|
55
|
-
if isinstance(target_container, list):
|
56
|
-
if not isinstance(index, int):
|
57
|
-
raise TypeError("Cannot use non-integer index on a list")
|
58
|
-
ensure_list_index(target_container, index)
|
59
|
-
if target_container[index] is None:
|
60
|
-
next_index = _indices[i + 1]
|
61
|
-
target_container[index] = (
|
62
|
-
[] if isinstance(next_index, int) else {}
|
63
|
-
)
|
64
|
-
elif isinstance(target_container, dict):
|
65
|
-
if isinstance(index, int):
|
66
|
-
raise TypeError(
|
67
|
-
f"Unsupported key type: {type(index).__name__}. "
|
68
|
-
"Only string keys are acceptable."
|
69
|
-
)
|
70
|
-
if index not in target_container:
|
71
|
-
next_index = _indices[i + 1]
|
72
|
-
target_container[index] = (
|
73
|
-
[] if isinstance(next_index, int) else {}
|
74
|
-
)
|
75
|
-
else:
|
76
|
-
raise TypeError("Target container is not a list or dictionary")
|
77
|
-
|
78
|
-
target_container = target_container[index]
|
79
|
-
|
80
|
-
last_index = _indices[-1]
|
81
|
-
if isinstance(target_container, list):
|
82
|
-
if not isinstance(last_index, int):
|
83
|
-
raise TypeError("Cannot use non-integer index on a list")
|
84
|
-
ensure_list_index(target_container, last_index)
|
85
|
-
target_container[last_index] = value
|
86
|
-
elif isinstance(target_container, dict):
|
87
|
-
if not isinstance(last_index, str):
|
88
|
-
raise TypeError(
|
89
|
-
f"Unsupported key type: {type(last_index).__name__}. "
|
90
|
-
"Only string keys are acceptable."
|
91
|
-
)
|
92
|
-
target_container[last_index] = value
|
93
|
-
else:
|
94
|
-
raise TypeError("Cannot set value on non-list/dict element")
|