lionagi 0.0.312__py3-none-any.whl → 0.2.1__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/__init__.py +61 -3
- lionagi/core/__init__.py +0 -14
- lionagi/core/_setting/_setting.py +59 -0
- lionagi/core/action/__init__.py +14 -0
- lionagi/core/action/function_calling.py +136 -0
- lionagi/core/action/manual.py +1 -0
- lionagi/core/action/node.py +109 -0
- lionagi/core/action/tool.py +114 -0
- lionagi/core/action/tool_manager.py +356 -0
- lionagi/core/agent/__init__.py +0 -3
- lionagi/core/agent/base_agent.py +45 -36
- lionagi/core/agent/eval/evaluator.py +1 -0
- lionagi/core/agent/eval/vote.py +40 -0
- lionagi/core/agent/learn/learner.py +59 -0
- lionagi/core/agent/plan/unit_template.py +1 -0
- lionagi/core/collections/__init__.py +17 -0
- lionagi/core/collections/_logger.py +319 -0
- lionagi/core/collections/abc/__init__.py +53 -0
- lionagi/core/collections/abc/component.py +615 -0
- lionagi/core/collections/abc/concepts.py +297 -0
- lionagi/core/collections/abc/exceptions.py +150 -0
- lionagi/core/collections/abc/util.py +45 -0
- lionagi/core/collections/exchange.py +161 -0
- lionagi/core/collections/flow.py +426 -0
- lionagi/core/collections/model.py +419 -0
- lionagi/core/collections/pile.py +913 -0
- lionagi/core/collections/progression.py +236 -0
- lionagi/core/collections/util.py +64 -0
- lionagi/core/director/direct.py +314 -0
- lionagi/core/director/director.py +2 -0
- lionagi/core/engine/branch_engine.py +333 -0
- lionagi/core/engine/instruction_map_engine.py +204 -0
- lionagi/core/engine/sandbox_.py +14 -0
- lionagi/core/engine/script_engine.py +99 -0
- lionagi/core/executor/base_executor.py +90 -0
- lionagi/core/executor/graph_executor.py +330 -0
- lionagi/core/executor/neo4j_executor.py +384 -0
- lionagi/core/generic/__init__.py +7 -0
- lionagi/core/generic/edge.py +112 -0
- lionagi/core/generic/edge_condition.py +16 -0
- lionagi/core/generic/graph.py +236 -0
- lionagi/core/generic/hyperedge.py +1 -0
- lionagi/core/generic/node.py +220 -0
- lionagi/core/generic/tree.py +48 -0
- lionagi/core/generic/tree_node.py +79 -0
- lionagi/core/mail/__init__.py +7 -3
- lionagi/core/mail/mail.py +25 -0
- lionagi/core/mail/mail_manager.py +142 -58
- lionagi/core/mail/package.py +45 -0
- lionagi/core/mail/start_mail.py +36 -0
- lionagi/core/message/__init__.py +19 -0
- lionagi/core/message/action_request.py +133 -0
- lionagi/core/message/action_response.py +135 -0
- lionagi/core/message/assistant_response.py +95 -0
- lionagi/core/message/instruction.py +234 -0
- lionagi/core/message/message.py +101 -0
- lionagi/core/message/system.py +86 -0
- lionagi/core/message/util.py +283 -0
- lionagi/core/report/__init__.py +4 -0
- lionagi/core/report/base.py +217 -0
- lionagi/core/report/form.py +231 -0
- lionagi/core/report/report.py +166 -0
- lionagi/core/report/util.py +28 -0
- lionagi/core/rule/__init__.py +0 -0
- lionagi/core/rule/_default.py +16 -0
- lionagi/core/rule/action.py +99 -0
- lionagi/core/rule/base.py +238 -0
- lionagi/core/rule/boolean.py +56 -0
- lionagi/core/rule/choice.py +47 -0
- lionagi/core/rule/mapping.py +96 -0
- lionagi/core/rule/number.py +71 -0
- lionagi/core/rule/rulebook.py +109 -0
- lionagi/core/rule/string.py +52 -0
- lionagi/core/rule/util.py +35 -0
- lionagi/core/session/__init__.py +0 -3
- lionagi/core/session/branch.py +431 -0
- lionagi/core/session/directive_mixin.py +287 -0
- lionagi/core/session/session.py +230 -902
- lionagi/core/structure/__init__.py +1 -0
- lionagi/core/structure/chain.py +1 -0
- lionagi/core/structure/forest.py +1 -0
- lionagi/core/structure/graph.py +1 -0
- lionagi/core/structure/tree.py +1 -0
- lionagi/core/unit/__init__.py +5 -0
- lionagi/core/unit/parallel_unit.py +245 -0
- lionagi/core/unit/template/__init__.py +0 -0
- lionagi/core/unit/template/action.py +81 -0
- lionagi/core/unit/template/base.py +51 -0
- lionagi/core/unit/template/plan.py +84 -0
- lionagi/core/unit/template/predict.py +109 -0
- lionagi/core/unit/template/score.py +124 -0
- lionagi/core/unit/template/select.py +104 -0
- lionagi/core/unit/unit.py +362 -0
- lionagi/core/unit/unit_form.py +305 -0
- lionagi/core/unit/unit_mixin.py +1168 -0
- lionagi/core/unit/util.py +71 -0
- lionagi/core/validator/__init__.py +0 -0
- lionagi/core/validator/validator.py +364 -0
- lionagi/core/work/__init__.py +0 -0
- lionagi/core/work/work.py +76 -0
- lionagi/core/work/work_function.py +101 -0
- lionagi/core/work/work_queue.py +103 -0
- lionagi/core/work/worker.py +258 -0
- lionagi/core/work/worklog.py +120 -0
- lionagi/experimental/__init__.py +0 -0
- lionagi/experimental/compressor/__init__.py +0 -0
- lionagi/experimental/compressor/base.py +46 -0
- lionagi/experimental/compressor/llm_compressor.py +247 -0
- lionagi/experimental/compressor/llm_summarizer.py +61 -0
- lionagi/experimental/compressor/util.py +70 -0
- lionagi/experimental/directive/__init__.py +19 -0
- lionagi/experimental/directive/parser/__init__.py +0 -0
- lionagi/experimental/directive/parser/base_parser.py +282 -0
- lionagi/experimental/directive/template/__init__.py +0 -0
- lionagi/experimental/directive/template/base_template.py +79 -0
- lionagi/experimental/directive/template/schema.py +36 -0
- lionagi/experimental/directive/tokenizer.py +73 -0
- lionagi/experimental/evaluator/__init__.py +0 -0
- lionagi/experimental/evaluator/ast_evaluator.py +131 -0
- lionagi/experimental/evaluator/base_evaluator.py +218 -0
- lionagi/experimental/knowledge/__init__.py +0 -0
- lionagi/experimental/knowledge/base.py +10 -0
- lionagi/experimental/knowledge/graph.py +0 -0
- lionagi/experimental/memory/__init__.py +0 -0
- lionagi/experimental/strategies/__init__.py +0 -0
- lionagi/experimental/strategies/base.py +1 -0
- lionagi/integrations/bridge/autogen_/__init__.py +0 -0
- lionagi/integrations/bridge/autogen_/autogen_.py +124 -0
- lionagi/integrations/bridge/langchain_/documents.py +4 -0
- lionagi/integrations/bridge/llamaindex_/index.py +30 -0
- lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +6 -0
- lionagi/integrations/bridge/llamaindex_/llama_pack.py +227 -0
- lionagi/integrations/bridge/llamaindex_/node_parser.py +6 -9
- lionagi/integrations/bridge/pydantic_/pydantic_bridge.py +1 -0
- lionagi/integrations/bridge/transformers_/__init__.py +0 -0
- lionagi/integrations/bridge/transformers_/install_.py +36 -0
- lionagi/integrations/chunker/__init__.py +0 -0
- lionagi/integrations/chunker/chunk.py +312 -0
- lionagi/integrations/config/oai_configs.py +38 -7
- lionagi/integrations/config/ollama_configs.py +1 -1
- lionagi/integrations/config/openrouter_configs.py +14 -2
- lionagi/integrations/loader/__init__.py +0 -0
- lionagi/integrations/loader/load.py +253 -0
- lionagi/integrations/loader/load_util.py +195 -0
- lionagi/integrations/provider/_mapping.py +46 -0
- lionagi/integrations/provider/litellm.py +2 -1
- lionagi/integrations/provider/mlx_service.py +16 -9
- lionagi/integrations/provider/oai.py +91 -4
- lionagi/integrations/provider/ollama.py +7 -6
- lionagi/integrations/provider/openrouter.py +115 -8
- lionagi/integrations/provider/services.py +2 -2
- lionagi/integrations/provider/transformers.py +18 -22
- lionagi/integrations/storage/__init__.py +3 -0
- lionagi/integrations/storage/neo4j.py +665 -0
- lionagi/integrations/storage/storage_util.py +287 -0
- lionagi/integrations/storage/structure_excel.py +285 -0
- lionagi/integrations/storage/to_csv.py +63 -0
- lionagi/integrations/storage/to_excel.py +83 -0
- lionagi/libs/__init__.py +26 -1
- lionagi/libs/ln_api.py +78 -23
- lionagi/libs/ln_context.py +37 -0
- lionagi/libs/ln_convert.py +21 -9
- lionagi/libs/ln_func_call.py +69 -28
- lionagi/libs/ln_image.py +107 -0
- lionagi/libs/ln_knowledge_graph.py +405 -0
- lionagi/libs/ln_nested.py +26 -11
- lionagi/libs/ln_parse.py +110 -14
- lionagi/libs/ln_queue.py +117 -0
- lionagi/libs/ln_tokenize.py +164 -0
- lionagi/{core/prompt/field_validator.py → libs/ln_validate.py} +79 -14
- lionagi/libs/special_tokens.py +172 -0
- lionagi/libs/sys_util.py +107 -2
- lionagi/lions/__init__.py +0 -0
- lionagi/lions/coder/__init__.py +0 -0
- lionagi/lions/coder/add_feature.py +20 -0
- lionagi/lions/coder/base_prompts.py +22 -0
- lionagi/lions/coder/code_form.py +13 -0
- lionagi/lions/coder/coder.py +168 -0
- lionagi/lions/coder/util.py +96 -0
- lionagi/lions/researcher/__init__.py +0 -0
- lionagi/lions/researcher/data_source/__init__.py +0 -0
- lionagi/lions/researcher/data_source/finhub_.py +191 -0
- lionagi/lions/researcher/data_source/google_.py +199 -0
- lionagi/lions/researcher/data_source/wiki_.py +96 -0
- lionagi/lions/researcher/data_source/yfinance_.py +21 -0
- lionagi/tests/integrations/__init__.py +0 -0
- lionagi/tests/libs/__init__.py +0 -0
- lionagi/tests/libs/test_field_validators.py +353 -0
- lionagi/tests/{test_libs → libs}/test_func_call.py +23 -21
- lionagi/tests/{test_libs → libs}/test_nested.py +36 -21
- lionagi/tests/{test_libs → libs}/test_parse.py +1 -1
- lionagi/tests/libs/test_queue.py +67 -0
- lionagi/tests/test_core/collections/__init__.py +0 -0
- lionagi/tests/test_core/collections/test_component.py +206 -0
- lionagi/tests/test_core/collections/test_exchange.py +138 -0
- lionagi/tests/test_core/collections/test_flow.py +145 -0
- lionagi/tests/test_core/collections/test_pile.py +171 -0
- lionagi/tests/test_core/collections/test_progression.py +129 -0
- lionagi/tests/test_core/generic/__init__.py +0 -0
- lionagi/tests/test_core/generic/test_edge.py +67 -0
- lionagi/tests/test_core/generic/test_graph.py +96 -0
- lionagi/tests/test_core/generic/test_node.py +106 -0
- lionagi/tests/test_core/generic/test_tree_node.py +73 -0
- lionagi/tests/test_core/test_branch.py +115 -292
- lionagi/tests/test_core/test_form.py +46 -0
- lionagi/tests/test_core/test_report.py +105 -0
- lionagi/tests/test_core/test_validator.py +111 -0
- lionagi/version.py +1 -1
- {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/LICENSE +12 -11
- {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/METADATA +19 -118
- lionagi-0.2.1.dist-info/RECORD +240 -0
- lionagi/core/branch/__init__.py +0 -4
- lionagi/core/branch/base_branch.py +0 -654
- lionagi/core/branch/branch.py +0 -471
- lionagi/core/branch/branch_flow_mixin.py +0 -96
- lionagi/core/branch/executable_branch.py +0 -347
- lionagi/core/branch/util.py +0 -323
- lionagi/core/direct/__init__.py +0 -6
- lionagi/core/direct/predict.py +0 -161
- lionagi/core/direct/score.py +0 -278
- lionagi/core/direct/select.py +0 -169
- lionagi/core/direct/utils.py +0 -87
- lionagi/core/direct/vote.py +0 -64
- lionagi/core/flow/base/baseflow.py +0 -23
- lionagi/core/flow/monoflow/ReAct.py +0 -238
- lionagi/core/flow/monoflow/__init__.py +0 -9
- lionagi/core/flow/monoflow/chat.py +0 -95
- lionagi/core/flow/monoflow/chat_mixin.py +0 -263
- lionagi/core/flow/monoflow/followup.py +0 -214
- lionagi/core/flow/polyflow/__init__.py +0 -1
- lionagi/core/flow/polyflow/chat.py +0 -248
- lionagi/core/mail/schema.py +0 -56
- lionagi/core/messages/__init__.py +0 -3
- lionagi/core/messages/schema.py +0 -533
- lionagi/core/prompt/prompt_template.py +0 -316
- lionagi/core/schema/__init__.py +0 -22
- lionagi/core/schema/action_node.py +0 -29
- lionagi/core/schema/base_mixin.py +0 -296
- lionagi/core/schema/base_node.py +0 -199
- lionagi/core/schema/condition.py +0 -24
- lionagi/core/schema/data_logger.py +0 -354
- lionagi/core/schema/data_node.py +0 -93
- lionagi/core/schema/prompt_template.py +0 -67
- lionagi/core/schema/structure.py +0 -910
- lionagi/core/tool/__init__.py +0 -3
- lionagi/core/tool/tool_manager.py +0 -280
- lionagi/integrations/bridge/pydantic_/base_model.py +0 -7
- lionagi/tests/test_core/test_base_branch.py +0 -427
- lionagi/tests/test_core/test_chat_flow.py +0 -63
- lionagi/tests/test_core/test_mail_manager.py +0 -75
- lionagi/tests/test_core/test_prompts.py +0 -51
- lionagi/tests/test_core/test_session.py +0 -254
- lionagi/tests/test_core/test_session_base_util.py +0 -312
- lionagi/tests/test_core/test_tool_manager.py +0 -95
- lionagi-0.0.312.dist-info/RECORD +0 -111
- /lionagi/core/{branch/base → _setting}/__init__.py +0 -0
- /lionagi/core/{flow → agent/eval}/__init__.py +0 -0
- /lionagi/core/{flow/base → agent/learn}/__init__.py +0 -0
- /lionagi/core/{prompt → agent/plan}/__init__.py +0 -0
- /lionagi/core/{tool/manual.py → agent/plan/plan.py} +0 -0
- /lionagi/{tests/test_integrations → core/director}/__init__.py +0 -0
- /lionagi/{tests/test_libs → core/engine}/__init__.py +0 -0
- /lionagi/{tests/test_libs/test_async.py → core/executor/__init__.py} +0 -0
- /lionagi/tests/{test_libs → libs}/test_api.py +0 -0
- /lionagi/tests/{test_libs → libs}/test_convert.py +0 -0
- /lionagi/tests/{test_libs → libs}/test_sys_util.py +0 -0
- {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/WHEEL +0 -0
- {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,353 @@
|
|
1
|
+
import unittest
|
2
|
+
from unittest.mock import patch
|
3
|
+
from lionagi.libs.ln_validate import (
|
4
|
+
check_dict_field,
|
5
|
+
check_action_field,
|
6
|
+
check_number_field,
|
7
|
+
check_bool_field,
|
8
|
+
check_str_field,
|
9
|
+
check_enum_field,
|
10
|
+
_fix_action_field,
|
11
|
+
_fix_number_field,
|
12
|
+
_fix_bool_field,
|
13
|
+
_fix_str_field,
|
14
|
+
_fix_enum_field,
|
15
|
+
)
|
16
|
+
|
17
|
+
|
18
|
+
class TestValidationFunctions(unittest.TestCase):
|
19
|
+
def test_check_dict_field_valid(self):
|
20
|
+
x = {"key": "value"}
|
21
|
+
keys = ["key"]
|
22
|
+
result = check_dict_field(x, keys)
|
23
|
+
self.assertEqual(result, x)
|
24
|
+
|
25
|
+
def test_fix_action_field_invalid_string(self):
|
26
|
+
x = '[{"invalid_key": "value"}]'
|
27
|
+
try:
|
28
|
+
_fix_action_field(x)
|
29
|
+
except ValueError as e:
|
30
|
+
self.assertEqual(str(e), "Invalid action field: {'invalid_key': 'value'}")
|
31
|
+
|
32
|
+
def test_fix_action_field_invalid_discard_disabled(self):
|
33
|
+
x = '[{"function": "func1", "arguments": {}}, {"invalid_key": "value"}]'
|
34
|
+
try:
|
35
|
+
_fix_action_field(x, discard_=False)
|
36
|
+
except ValueError as e:
|
37
|
+
self.assertEqual(str(e), "Invalid action field: {'invalid_key': 'value'}")
|
38
|
+
|
39
|
+
def test_check_dict_field_invalid_fixable(self):
|
40
|
+
x = '{"key": "value"}'
|
41
|
+
keys = ["key"]
|
42
|
+
result = check_dict_field(x, keys, fix_=True)
|
43
|
+
self.assertEqual(result, {"key": "value"})
|
44
|
+
|
45
|
+
def test_check_dict_field_invalid_not_fixable(self):
|
46
|
+
x = '{"invalid_key": "value"}'
|
47
|
+
keys = ["key"]
|
48
|
+
with self.assertRaises(ValueError) as cm:
|
49
|
+
check_dict_field(x, keys, fix_=False)
|
50
|
+
self.assertEqual(
|
51
|
+
str(cm.exception), "Default value for DICT must be a dict, got str"
|
52
|
+
)
|
53
|
+
|
54
|
+
def test_check_action_field_invalid_not_fixable(self):
|
55
|
+
x = '[{"invalid_key": "value"}]'
|
56
|
+
try:
|
57
|
+
check_action_field(x, fix_=False)
|
58
|
+
except ValueError as e:
|
59
|
+
self.assertEqual(str(e), "Invalid action field type.")
|
60
|
+
|
61
|
+
def test_check_action_field_invalid_fix_disabled(self):
|
62
|
+
x = '[{"function": "func1", "arguments": {}}, {"invalid_key": "value"}]'
|
63
|
+
try:
|
64
|
+
check_action_field(x, fix_=False)
|
65
|
+
except ValueError as e:
|
66
|
+
self.assertEqual(str(e), "Invalid action field type.")
|
67
|
+
|
68
|
+
def test_check_action_field_valid(self):
|
69
|
+
x = [
|
70
|
+
{"function": "func1", "arguments": {}},
|
71
|
+
{"function": "func2", "arguments": {}},
|
72
|
+
]
|
73
|
+
result = check_action_field(x)
|
74
|
+
self.assertEqual(result, x)
|
75
|
+
|
76
|
+
def test_check_action_field_invalid_fixable(self):
|
77
|
+
x = '[{"function": "func1", "arguments": {}}, {"function": "func2", "arguments": {}}]'
|
78
|
+
result = check_action_field(x, fix_=True)
|
79
|
+
self.assertEqual(
|
80
|
+
result,
|
81
|
+
[
|
82
|
+
{"function": "func1", "arguments": {}},
|
83
|
+
{"function": "func2", "arguments": {}},
|
84
|
+
],
|
85
|
+
)
|
86
|
+
|
87
|
+
def test_check_action_field_invalid_fix_disabled(self):
|
88
|
+
x = '[{"function": "func1", "arguments": {}}, {"invalid_key": "value"}]'
|
89
|
+
try:
|
90
|
+
check_action_field(x, fix_=False)
|
91
|
+
except ValueError as e:
|
92
|
+
self.assertEqual(str(e), "Invalid action field type.")
|
93
|
+
|
94
|
+
def test_check_number_field_valid(self):
|
95
|
+
x = 42
|
96
|
+
result = check_number_field(x)
|
97
|
+
self.assertEqual(result, x)
|
98
|
+
|
99
|
+
def test_check_number_field_invalid_fixable(self):
|
100
|
+
x = "42"
|
101
|
+
result = check_number_field(x, fix_=True)
|
102
|
+
self.assertEqual(result, 42)
|
103
|
+
|
104
|
+
def test_check_number_field_invalid_not_fixable(self):
|
105
|
+
x = "not_a_number"
|
106
|
+
with self.assertRaises(ValueError) as cm:
|
107
|
+
check_number_field(x, fix_=True)
|
108
|
+
self.assertEqual(
|
109
|
+
str(cm.exception), "Failed to convert not_a_number into a numeric value"
|
110
|
+
)
|
111
|
+
|
112
|
+
def test_check_number_field_invalid_fix_disabled(self):
|
113
|
+
x = "42"
|
114
|
+
with self.assertRaises(ValueError) as cm:
|
115
|
+
check_number_field(x, fix_=False)
|
116
|
+
self.assertEqual(
|
117
|
+
str(cm.exception),
|
118
|
+
"Default value for NUMERIC must be an int or float, got str",
|
119
|
+
)
|
120
|
+
|
121
|
+
def test_check_bool_field_valid(self):
|
122
|
+
x = True
|
123
|
+
result = check_bool_field(x)
|
124
|
+
self.assertEqual(result, x)
|
125
|
+
|
126
|
+
def test_check_bool_field_invalid_fixable(self):
|
127
|
+
x = "true"
|
128
|
+
result = check_bool_field(x, fix_=True)
|
129
|
+
self.assertTrue(result)
|
130
|
+
|
131
|
+
def test_check_bool_field_invalid_not_fixable(self):
|
132
|
+
x = "not_a_boolean"
|
133
|
+
with self.assertRaises(ValueError) as cm:
|
134
|
+
check_bool_field(x, fix_=True)
|
135
|
+
self.assertEqual(
|
136
|
+
str(cm.exception), "Failed to convert not_a_boolean into a boolean value"
|
137
|
+
)
|
138
|
+
|
139
|
+
def test_check_bool_field_invalid_fix_disabled(self):
|
140
|
+
x = "true"
|
141
|
+
with self.assertRaises(ValueError) as cm:
|
142
|
+
check_bool_field(x, fix_=False)
|
143
|
+
self.assertEqual(
|
144
|
+
str(cm.exception), "Default value for BOOLEAN must be a bool, got str"
|
145
|
+
)
|
146
|
+
|
147
|
+
def test_check_str_field_valid(self):
|
148
|
+
x = "hello"
|
149
|
+
result = check_str_field(x)
|
150
|
+
self.assertEqual(result, x)
|
151
|
+
|
152
|
+
def test_check_str_field_invalid_fixable(self):
|
153
|
+
x = 42
|
154
|
+
result = check_str_field(x, fix_=True)
|
155
|
+
self.assertEqual(result, "42")
|
156
|
+
|
157
|
+
def test_check_str_field_invalid_not_fixable(self):
|
158
|
+
x = object()
|
159
|
+
try:
|
160
|
+
check_str_field(x, fix_=False)
|
161
|
+
except ValueError as e:
|
162
|
+
self.assertEqual(
|
163
|
+
str(e), "Default value for STRING must be a str, got object"
|
164
|
+
)
|
165
|
+
|
166
|
+
def test_check_str_field_invalid_fix_disabled(self):
|
167
|
+
x = 42
|
168
|
+
with self.assertRaises(ValueError) as cm:
|
169
|
+
check_str_field(x, fix_=False)
|
170
|
+
self.assertEqual(
|
171
|
+
str(cm.exception), "Default value for STRING must be a str, got int"
|
172
|
+
)
|
173
|
+
|
174
|
+
def test_check_enum_field_valid(self):
|
175
|
+
x = "option1"
|
176
|
+
choices = ["option1", "option2"]
|
177
|
+
result = check_enum_field(x, choices)
|
178
|
+
self.assertEqual(result, x)
|
179
|
+
|
180
|
+
def test_check_enum_field_invalid_fixable(self):
|
181
|
+
x = "option3"
|
182
|
+
choices = ["option1", "option2"]
|
183
|
+
with patch("lionagi.libs.ln_validate._fix_enum_field", return_value="option1"):
|
184
|
+
result = check_enum_field(x, choices, fix_=True)
|
185
|
+
self.assertEqual(result, "option1")
|
186
|
+
|
187
|
+
def test_check_enum_field_invalid_not_fixable(self):
|
188
|
+
x = "option3"
|
189
|
+
choices = ["option1", "option2"]
|
190
|
+
with self.assertRaises(ValueError) as cm:
|
191
|
+
check_enum_field(x, choices, fix_=False)
|
192
|
+
self.assertEqual(
|
193
|
+
str(cm.exception),
|
194
|
+
"Default value for ENUM must be one of the ['option1', 'option2'], got option3",
|
195
|
+
)
|
196
|
+
|
197
|
+
def test_check_enum_field_invalid_choices(self):
|
198
|
+
x = "option1"
|
199
|
+
choices = ["option1", 42]
|
200
|
+
with self.assertRaises(ValueError) as cm:
|
201
|
+
check_enum_field(x, choices)
|
202
|
+
self.assertEqual(
|
203
|
+
str(cm.exception),
|
204
|
+
"Field type ENUM requires all choices to be of the same type, got ['option1', 42]",
|
205
|
+
)
|
206
|
+
|
207
|
+
def test_check_enum_field_invalid_type(self):
|
208
|
+
x = 42
|
209
|
+
choices = ["option1", "option2"]
|
210
|
+
with self.assertRaises(ValueError) as cm:
|
211
|
+
check_enum_field(x, choices)
|
212
|
+
self.assertEqual(
|
213
|
+
str(cm.exception),
|
214
|
+
"Default value for ENUM must be an instance of the str, got int",
|
215
|
+
)
|
216
|
+
|
217
|
+
def test_fix_action_field_string(self):
|
218
|
+
x = '[{"function": "func1", "arguments": {}}, {"function": "func2", "arguments": {}}]'
|
219
|
+
result = _fix_action_field(x)
|
220
|
+
self.assertEqual(
|
221
|
+
result,
|
222
|
+
[
|
223
|
+
{"function": "func1", "arguments": {}},
|
224
|
+
{"function": "func2", "arguments": {}},
|
225
|
+
],
|
226
|
+
)
|
227
|
+
|
228
|
+
def test_fix_action_field_invalid_string(self):
|
229
|
+
x = '[{"invalid_key": "value"}]'
|
230
|
+
try:
|
231
|
+
_fix_action_field(x)
|
232
|
+
except ValueError as e:
|
233
|
+
self.assertEqual(str(e), "Invalid action field: {'invalid_key': 'value'}")
|
234
|
+
|
235
|
+
def test_fix_action_field_invalid_discard_disabled(self):
|
236
|
+
x = '[{"function": "func1", "arguments": {}}, {"invalid_key": "value"}]'
|
237
|
+
try:
|
238
|
+
_fix_action_field(x, discard_=False)
|
239
|
+
except ValueError as e:
|
240
|
+
self.assertEqual(str(e), "Invalid action field: {'invalid_key': 'value'}")
|
241
|
+
|
242
|
+
def test_fix_number_field_valid(self):
|
243
|
+
x = "42"
|
244
|
+
result = _fix_number_field(x)
|
245
|
+
self.assertEqual(result, 42)
|
246
|
+
|
247
|
+
def test_fix_number_field_invalid(self):
|
248
|
+
x = "not_a_number"
|
249
|
+
with self.assertRaises(ValueError) as cm:
|
250
|
+
_fix_number_field(x)
|
251
|
+
self.assertEqual(
|
252
|
+
str(cm.exception), "Failed to convert not_a_number into a numeric value"
|
253
|
+
)
|
254
|
+
|
255
|
+
def test_fix_bool_field_true(self):
|
256
|
+
x = "true"
|
257
|
+
result = _fix_bool_field(x)
|
258
|
+
self.assertTrue(result)
|
259
|
+
|
260
|
+
def test_fix_bool_field_false(self):
|
261
|
+
x = "false"
|
262
|
+
result = _fix_bool_field(x)
|
263
|
+
self.assertFalse(result)
|
264
|
+
|
265
|
+
def test_fix_bool_field_invalid(self):
|
266
|
+
x = "not_a_boolean"
|
267
|
+
with self.assertRaises(ValueError) as cm:
|
268
|
+
_fix_bool_field(x)
|
269
|
+
self.assertEqual(
|
270
|
+
str(cm.exception), "Failed to convert not_a_boolean into a boolean value"
|
271
|
+
)
|
272
|
+
|
273
|
+
def test_fix_str_field_valid(self):
|
274
|
+
x = 42
|
275
|
+
result = _fix_str_field(x)
|
276
|
+
self.assertEqual(result, "42")
|
277
|
+
|
278
|
+
def test_fix_str_field_invalid(self):
|
279
|
+
x = object()
|
280
|
+
try:
|
281
|
+
_fix_str_field(x)
|
282
|
+
except ValueError as e:
|
283
|
+
self.assertEqual(
|
284
|
+
str(e),
|
285
|
+
"Failed to convert <object object at 0x{:x}> into a string value".format(
|
286
|
+
id(x)
|
287
|
+
),
|
288
|
+
)
|
289
|
+
|
290
|
+
def test_fix_enum_field_valid(self):
|
291
|
+
x = "option3"
|
292
|
+
choices = ["option1", "option2"]
|
293
|
+
with patch(
|
294
|
+
"lionagi.libs.StringMatch.choose_most_similar", return_value="option1"
|
295
|
+
):
|
296
|
+
result = _fix_enum_field(x, choices)
|
297
|
+
self.assertEqual(result, "option1")
|
298
|
+
|
299
|
+
def test_fix_enum_field_invalid(self):
|
300
|
+
x = "option3"
|
301
|
+
choices = ["option1", "option2"]
|
302
|
+
with patch(
|
303
|
+
"lionagi.libs.StringMatch.choose_most_similar",
|
304
|
+
side_effect=ValueError("No match found"),
|
305
|
+
):
|
306
|
+
with self.assertRaises(ValueError) as cm:
|
307
|
+
_fix_enum_field(x, choices)
|
308
|
+
self.assertEqual(
|
309
|
+
str(cm.exception), "Failed to convert option3 into one of the choices"
|
310
|
+
)
|
311
|
+
|
312
|
+
def test_fix_bool_field_false(self):
|
313
|
+
x = "false"
|
314
|
+
result = _fix_bool_field(x)
|
315
|
+
self.assertFalse(result)
|
316
|
+
|
317
|
+
def test_fix_str_field_invalid(self):
|
318
|
+
x = object()
|
319
|
+
try:
|
320
|
+
_fix_str_field(x)
|
321
|
+
except ValueError as e:
|
322
|
+
self.assertEqual(
|
323
|
+
str(e),
|
324
|
+
"Failed to convert <object object at 0x{:x}> into a string value".format(
|
325
|
+
id(x)
|
326
|
+
),
|
327
|
+
)
|
328
|
+
|
329
|
+
def test_fix_enum_field_valid(self):
|
330
|
+
x = "option3"
|
331
|
+
choices = ["option1", "option2"]
|
332
|
+
with patch(
|
333
|
+
"lionagi.libs.StringMatch.choose_most_similar", return_value="option1"
|
334
|
+
):
|
335
|
+
result = _fix_enum_field(x, choices)
|
336
|
+
self.assertEqual(result, "option1")
|
337
|
+
|
338
|
+
def test_fix_enum_field_invalid(self):
|
339
|
+
x = "option3"
|
340
|
+
choices = ["option1", "option2"]
|
341
|
+
with patch(
|
342
|
+
"lionagi.libs.StringMatch.choose_most_similar",
|
343
|
+
side_effect=ValueError("No match found"),
|
344
|
+
):
|
345
|
+
with self.assertRaises(ValueError) as cm:
|
346
|
+
_fix_enum_field(x, choices)
|
347
|
+
self.assertEqual(
|
348
|
+
str(cm.exception), "Failed to convert option3 into one of the choices"
|
349
|
+
)
|
350
|
+
|
351
|
+
|
352
|
+
if __name__ == "__main__":
|
353
|
+
unittest.main()
|
@@ -296,8 +296,8 @@ class TestRCall(unittest.IsolatedAsyncioTestCase):
|
|
296
296
|
await asyncio.sleep(2)
|
297
297
|
return x
|
298
298
|
|
299
|
-
with self.assertRaises(
|
300
|
-
await rcall(async_func, 5, timeout=1)
|
299
|
+
with self.assertRaises(RuntimeError):
|
300
|
+
await rcall(async_func, 5, timeout=0.1)
|
301
301
|
|
302
302
|
async def test_retry_mechanism(self):
|
303
303
|
attempt_count = 0
|
@@ -307,22 +307,22 @@ class TestRCall(unittest.IsolatedAsyncioTestCase):
|
|
307
307
|
attempt_count += 1
|
308
308
|
raise ValueError("Test Error")
|
309
309
|
|
310
|
-
with self.assertRaises(
|
311
|
-
await rcall(sync_func, 5, retries=3)
|
310
|
+
with self.assertRaises(RuntimeError):
|
311
|
+
await rcall(sync_func, 5, retries=3, delay=0.1, backoff_factor=1.1)
|
312
312
|
self.assertEqual(attempt_count, 3) # Initial call + 3 retries
|
313
313
|
|
314
314
|
async def test_default_value_on_exception(self):
|
315
315
|
def sync_func(x):
|
316
316
|
raise ValueError("Test Error")
|
317
317
|
|
318
|
-
result = await rcall(sync_func, 5, default=10)
|
318
|
+
result = await rcall(sync_func, 5, default=10, delay=0)
|
319
319
|
self.assertEqual(result, 10)
|
320
320
|
|
321
321
|
async def test_exception_propagation(self):
|
322
322
|
def sync_func(x):
|
323
323
|
raise ValueError("Test Error")
|
324
324
|
|
325
|
-
with self.assertRaises(
|
325
|
+
with self.assertRaises(RuntimeError):
|
326
326
|
await rcall(sync_func, 5)
|
327
327
|
|
328
328
|
|
@@ -581,22 +581,22 @@ class TestCallDecorator(unittest.IsolatedAsyncioTestCase):
|
|
581
581
|
class TestThrottleClass(unittest.TestCase):
|
582
582
|
|
583
583
|
def test_throttling_behavior_sync(self):
|
584
|
-
throttle_decorator = Throttle(
|
584
|
+
throttle_decorator = Throttle(0.1) # 2 seconds throttle period
|
585
585
|
|
586
586
|
@throttle_decorator
|
587
587
|
def test_func():
|
588
588
|
return time.time()
|
589
589
|
|
590
590
|
first_call_time = test_func()
|
591
|
-
time.sleep(1) # Sleep less than the throttle period
|
591
|
+
time.sleep(0.1) # Sleep less than the throttle period
|
592
592
|
second_call_time = test_func()
|
593
593
|
|
594
594
|
self.assertGreaterEqual(
|
595
|
-
second_call_time - first_call_time,
|
595
|
+
second_call_time - first_call_time, 0.1
|
596
596
|
) # Second call should be throttled
|
597
597
|
|
598
598
|
def test_throttling_behavior_async(self):
|
599
|
-
throttle_decorator = Throttle(
|
599
|
+
throttle_decorator = Throttle(1) # 2 seconds throttle period
|
600
600
|
|
601
601
|
@throttle_decorator
|
602
602
|
async def test_func():
|
@@ -604,28 +604,28 @@ class TestThrottleClass(unittest.TestCase):
|
|
604
604
|
|
605
605
|
async def async_test():
|
606
606
|
first_call_time = await test_func()
|
607
|
-
await asyncio.sleep(1) # Sleep less than the throttle period
|
607
|
+
await asyncio.sleep(0.1) # Sleep less than the throttle period
|
608
608
|
second_call_time = await test_func()
|
609
609
|
|
610
610
|
self.assertGreaterEqual(
|
611
|
-
second_call_time - first_call_time,
|
611
|
+
second_call_time - first_call_time, 1
|
612
612
|
) # Second call should be throttled
|
613
613
|
|
614
614
|
asyncio.run(async_test())
|
615
615
|
|
616
616
|
def test_successive_calls_with_sufficient_delay(self):
|
617
|
-
throttle_decorator = Throttle(1) # 1 second throttle period
|
617
|
+
throttle_decorator = Throttle(0.1) # 1 second throttle period
|
618
618
|
|
619
619
|
@throttle_decorator
|
620
620
|
def test_func():
|
621
621
|
return time.time()
|
622
622
|
|
623
623
|
first_call_time = test_func()
|
624
|
-
time.sleep(
|
624
|
+
time.sleep(0.12) # Sleep more than the throttle period
|
625
625
|
second_call_time = test_func()
|
626
626
|
|
627
627
|
self.assertLess(
|
628
|
-
second_call_time - first_call_time,
|
628
|
+
second_call_time - first_call_time, 0.2
|
629
629
|
) # Second call should not be throttled
|
630
630
|
|
631
631
|
|
@@ -633,7 +633,7 @@ class TestAsyncRetryDecorator(unittest.IsolatedAsyncioTestCase):
|
|
633
633
|
async def test_successful_retry(self):
|
634
634
|
attempt = 0
|
635
635
|
|
636
|
-
@CallDecorator.retry(retries=3, delay=1, backoff_factor=2)
|
636
|
+
@CallDecorator.retry(retries=3, delay=0.1, backoff_factor=2)
|
637
637
|
async def test_func():
|
638
638
|
nonlocal attempt
|
639
639
|
attempt += 1
|
@@ -648,20 +648,22 @@ class TestAsyncRetryDecorator(unittest.IsolatedAsyncioTestCase):
|
|
648
648
|
async def test_retry_limit(self):
|
649
649
|
attempt = 0
|
650
650
|
|
651
|
-
@CallDecorator.retry(retries=2, delay=1, backoff_factor=2)
|
651
|
+
@CallDecorator.retry(retries=2, delay=0.1, backoff_factor=2)
|
652
652
|
async def test_func():
|
653
653
|
nonlocal attempt
|
654
654
|
attempt += 1
|
655
655
|
raise ValueError("Test failure")
|
656
656
|
|
657
|
-
|
657
|
+
try:
|
658
658
|
await test_func()
|
659
|
+
except:
|
660
|
+
pass
|
659
661
|
self.assertEqual(attempt, 2)
|
660
662
|
|
661
663
|
async def test_immediate_success(self):
|
662
664
|
attempt = 0
|
663
665
|
|
664
|
-
@CallDecorator.retry(retries=3, delay=1, backoff_factor=2)
|
666
|
+
@CallDecorator.retry(retries=3, delay=0.1, backoff_factor=2)
|
665
667
|
async def test_func():
|
666
668
|
nonlocal attempt
|
667
669
|
attempt += 1
|
@@ -675,7 +677,7 @@ class TestAsyncRetryDecorator(unittest.IsolatedAsyncioTestCase):
|
|
675
677
|
attempt = 0
|
676
678
|
start_time = time.time()
|
677
679
|
|
678
|
-
@CallDecorator.retry(retries=3, delay=1, backoff_factor=2)
|
680
|
+
@CallDecorator.retry(retries=3, delay=0.1, backoff_factor=2)
|
679
681
|
async def test_func():
|
680
682
|
nonlocal attempt
|
681
683
|
attempt += 1
|
@@ -690,7 +692,7 @@ class TestAsyncRetryDecorator(unittest.IsolatedAsyncioTestCase):
|
|
690
692
|
self.assertEqual(result, "Success")
|
691
693
|
# Note: The actual delay might be slightly longer than expected due to asyncio's event loop scheduling.
|
692
694
|
self.assertTrue(
|
693
|
-
elapsed_time >= 3, f"Elapsed time was {elapsed_time}, expected >= 3"
|
695
|
+
elapsed_time >= 0.3, f"Elapsed time was {elapsed_time}, expected >= 3"
|
694
696
|
)
|
695
697
|
self.assertEqual(attempt, 3)
|
696
698
|
|
@@ -39,7 +39,7 @@ class TestNSet(unittest.TestCase):
|
|
39
39
|
"""Test flattening and then unflattening a nested dictionary."""
|
40
40
|
original = {"a": {"b": {"c": 1}}}
|
41
41
|
flattened = flatten(original)
|
42
|
-
self.assertEqual(flattened, {"
|
42
|
+
self.assertEqual(flattened, {"a[^_^]b[^_^]c": 1})
|
43
43
|
unflattened = unflatten(flattened)
|
44
44
|
self.assertEqual(original, unflattened)
|
45
45
|
|
@@ -225,27 +225,36 @@ class TestFlatten(unittest.TestCase):
|
|
225
225
|
def test_flatten_nested_dict(self):
|
226
226
|
nested_dict = {"a": {"b": 1, "c": {"d": 2}}}
|
227
227
|
result = flatten(nested_dict)
|
228
|
-
self.assertEqual(result, {"
|
228
|
+
self.assertEqual(result, {"a[^_^]b": 1, "a[^_^]c[^_^]d": 2})
|
229
229
|
|
230
230
|
def test_flatten_nested_list(self):
|
231
231
|
nested_list = [[1, 2], [3, [4, 5]]]
|
232
232
|
result = flatten(nested_list)
|
233
|
-
self.assertEqual(
|
233
|
+
self.assertEqual(
|
234
|
+
result,
|
235
|
+
{
|
236
|
+
"0[^_^]0": 1,
|
237
|
+
"0[^_^]1": 2,
|
238
|
+
"1[^_^]0": 3,
|
239
|
+
"1[^_^]1[^_^]0": 4,
|
240
|
+
"1[^_^]1[^_^]1": 5,
|
241
|
+
},
|
242
|
+
)
|
234
243
|
|
235
244
|
def test_flatten_with_max_depth(self):
|
236
245
|
nested_dict = {"a": {"b": {"c": 1}}}
|
237
246
|
result = flatten(nested_dict, max_depth=1)
|
238
|
-
self.assertEqual(result, {"
|
247
|
+
self.assertEqual(result, {"a[^_^]b": {"c": 1}})
|
239
248
|
|
240
249
|
def test_flatten_dict_only(self):
|
241
250
|
nested_mix = {"a": [1, 2], "b": {"c": 3}}
|
242
251
|
result = flatten(nested_mix, dict_only=True)
|
243
|
-
self.assertEqual(result, {"a": [1, 2], "
|
252
|
+
self.assertEqual(result, {"a": [1, 2], "b[^_^]c": 3})
|
244
253
|
|
245
254
|
def test_flatten_inplace(self):
|
246
255
|
nested_dict = {"a": {"b": 1}}
|
247
256
|
flatten(nested_dict, inplace=True)
|
248
|
-
self.assertEqual(nested_dict, {"
|
257
|
+
self.assertEqual(nested_dict, {"a[^_^]b": 1})
|
249
258
|
|
250
259
|
def test_flatten_empty_structure(self):
|
251
260
|
self.assertEqual(flatten({}), {})
|
@@ -254,18 +263,18 @@ class TestFlatten(unittest.TestCase):
|
|
254
263
|
def test_flatten_deeply_nested_structure(self):
|
255
264
|
deeply_nested = {"a": {"b": {"c": {"d": 1}}}}
|
256
265
|
result = flatten(deeply_nested)
|
257
|
-
self.assertEqual(result, {"
|
266
|
+
self.assertEqual(result, {"a[^_^]b[^_^]c[^_^]d": 1})
|
258
267
|
|
259
268
|
|
260
269
|
class TestUnflatten(unittest.TestCase):
|
261
270
|
|
262
271
|
def test_unflatten_to_nested_dict(self):
|
263
|
-
flat_dict = {"
|
272
|
+
flat_dict = {"a[^_^]b": 1, "a[^_^]c[^_^]d": 2}
|
264
273
|
result = unflatten(flat_dict)
|
265
274
|
self.assertEqual(result, {"a": {"b": 1, "c": {"d": 2}}})
|
266
275
|
|
267
276
|
def test_unflatten_to_nested_list(self):
|
268
|
-
flat_dict = {"
|
277
|
+
flat_dict = {"0[^_^]0": 1, "0[^_^]1": 2, "1": [3, 4]}
|
269
278
|
result = unflatten(flat_dict)
|
270
279
|
self.assertEqual(result, [[1, 2], [3, 4]])
|
271
280
|
|
@@ -275,26 +284,26 @@ class TestUnflatten(unittest.TestCase):
|
|
275
284
|
self.assertEqual(result, {"a": {"b": 1, "c": {"d": 2}}})
|
276
285
|
|
277
286
|
def test_unflatten_with_custom_logic(self):
|
278
|
-
flat_dict = {"
|
287
|
+
flat_dict = {"a[^_^]1": "one", "a[^_^]2": "two"}
|
279
288
|
custom_logic = lambda key: int(key) if key.isdigit() else key
|
280
289
|
result = unflatten(flat_dict, custom_logic=custom_logic)
|
281
290
|
self.assertEqual(result, {"a": [None, "one", "two"]})
|
282
291
|
|
283
292
|
def test_unflatten_with_max_depth(self):
|
284
|
-
flat_dict = {"
|
293
|
+
flat_dict = {"a[^_^]b[^_^]c": 1, "a[^_^]b[^_^]d": 2}
|
285
294
|
result = unflatten(flat_dict, max_depth=1)
|
286
|
-
self.assertEqual(result, {"a": {"
|
295
|
+
self.assertEqual(result, {"a": {"b[^_^]c": 1, "b[^_^]d": 2}})
|
287
296
|
|
288
297
|
def test_unflatten_with_max_depth_int(self):
|
289
|
-
flat_dict = {"
|
298
|
+
flat_dict = {"0[^_^]0[^_^]0": 1, "0[^_^]1[^_^]0": 2}
|
290
299
|
result = unflatten(flat_dict, max_depth=1)
|
291
|
-
self.assertEqual(result, [[{"
|
300
|
+
self.assertEqual(result, [[{"0[^_^]0": 1}, {"1[^_^]0": 2}]])
|
292
301
|
|
293
302
|
def test_unflatten_empty_flat_dict(self):
|
294
303
|
self.assertEqual(unflatten({}), [])
|
295
304
|
|
296
305
|
def test_unflatten_to_mixed_structure(self):
|
297
|
-
flat_dict = {"
|
306
|
+
flat_dict = {"0[^_^]0": 1, "1[^_^]key": 2}
|
298
307
|
result = unflatten(flat_dict)
|
299
308
|
self.assertEqual(result, [[1], {"key": 2}])
|
300
309
|
|
@@ -319,7 +328,7 @@ class TestNInsert(unittest.TestCase):
|
|
319
328
|
def test_insert_with_max_depth(self):
|
320
329
|
obj = {}
|
321
330
|
ninsert(obj, ["a", "b", "c"], "value", max_depth=1)
|
322
|
-
self.assertEqual(obj, {"a": {"
|
331
|
+
self.assertEqual(obj, {"a": {"b[^_^]c": "value"}})
|
323
332
|
|
324
333
|
def test_insert_into_empty_structure(self):
|
325
334
|
obj = {}
|
@@ -331,24 +340,30 @@ class TestGetFlattenedKeys(unittest.TestCase):
|
|
331
340
|
|
332
341
|
def test_get_keys_from_nested_dict(self):
|
333
342
|
nested_dict = {"a": {"b": 1, "c": {"d": 2}}}
|
334
|
-
expected_keys = ["
|
343
|
+
expected_keys = ["a[^_^]b", "a[^_^]c[^_^]d"]
|
335
344
|
self.assertEqual(set(get_flattened_keys(nested_dict)), set(expected_keys))
|
336
345
|
|
337
346
|
def test_get_keys_from_nested_list(self):
|
338
347
|
nested_list = [[1, 2], [3, [4, 5]]]
|
339
|
-
expected_keys = [
|
348
|
+
expected_keys = [
|
349
|
+
"0[^_^]0",
|
350
|
+
"0[^_^]1",
|
351
|
+
"1[^_^]0",
|
352
|
+
"1[^_^]1[^_^]0",
|
353
|
+
"1[^_^]1[^_^]1",
|
354
|
+
]
|
340
355
|
self.assertEqual(set(get_flattened_keys(nested_list)), set(expected_keys))
|
341
356
|
|
342
357
|
def test_get_keys_with_max_depth(self):
|
343
358
|
nested_dict = {"a": {"b": {"c": 1}}}
|
344
|
-
expected_keys = ["
|
359
|
+
expected_keys = ["a[^_^]b"]
|
345
360
|
self.assertEqual(
|
346
361
|
set(get_flattened_keys(nested_dict, max_depth=1)), set(expected_keys)
|
347
362
|
)
|
348
363
|
|
349
364
|
def test_get_keys_dict_only(self):
|
350
365
|
nested_mix = {"a": [1, 2], "b": {"c": 3}}
|
351
|
-
expected_keys = ["a", "
|
366
|
+
expected_keys = ["a", "b[^_^]c"]
|
352
367
|
self.assertEqual(
|
353
368
|
set(get_flattened_keys(nested_mix, dict_only=True)), set(expected_keys)
|
354
369
|
)
|
@@ -358,7 +373,7 @@ class TestGetFlattenedKeys(unittest.TestCase):
|
|
358
373
|
|
359
374
|
def test_keys_from_deeply_nested_structure(self):
|
360
375
|
deeply_nested = {"a": {"b": {"c": {"d": 1}}}}
|
361
|
-
expected_keys = ["
|
376
|
+
expected_keys = ["a[^_^]b[^_^]c[^_^]d"]
|
362
377
|
self.assertEqual(set(get_flattened_keys(deeply_nested)), set(expected_keys))
|
363
378
|
|
364
379
|
|
@@ -55,7 +55,7 @@ class TestFuzzyParseJson(unittest.TestCase):
|
|
55
55
|
def test_extract_code_block(self):
|
56
56
|
"""Test extracting and parsing code blocks from Markdown."""
|
57
57
|
markdown = '```python\nprint("Hello, world!")\n```'
|
58
|
-
result = ParseUtil.
|
58
|
+
result = ParseUtil.extract_json_block(
|
59
59
|
markdown, language="python", parser=lambda x: x
|
60
60
|
)
|
61
61
|
self.assertEqual(result, 'print("Hello, world!")')
|