lionagi 0.2.11__py3-none-any.whl → 0.3.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. lionagi/core/action/function_calling.py +13 -6
  2. lionagi/core/action/tool.py +10 -9
  3. lionagi/core/action/tool_manager.py +18 -9
  4. lionagi/core/agent/README.md +1 -1
  5. lionagi/core/agent/base_agent.py +5 -2
  6. lionagi/core/agent/eval/README.md +1 -1
  7. lionagi/core/collections/README.md +1 -1
  8. lionagi/core/collections/_logger.py +16 -6
  9. lionagi/core/collections/abc/README.md +1 -1
  10. lionagi/core/collections/abc/component.py +35 -11
  11. lionagi/core/collections/abc/concepts.py +5 -3
  12. lionagi/core/collections/abc/exceptions.py +3 -1
  13. lionagi/core/collections/flow.py +16 -5
  14. lionagi/core/collections/model.py +34 -8
  15. lionagi/core/collections/pile.py +65 -28
  16. lionagi/core/collections/progression.py +1 -2
  17. lionagi/core/collections/util.py +11 -2
  18. lionagi/core/director/README.md +1 -1
  19. lionagi/core/engine/branch_engine.py +35 -10
  20. lionagi/core/engine/instruction_map_engine.py +14 -5
  21. lionagi/core/engine/sandbox_.py +3 -1
  22. lionagi/core/engine/script_engine.py +6 -2
  23. lionagi/core/executor/base_executor.py +10 -3
  24. lionagi/core/executor/graph_executor.py +12 -4
  25. lionagi/core/executor/neo4j_executor.py +18 -6
  26. lionagi/core/generic/edge.py +7 -2
  27. lionagi/core/generic/graph.py +23 -7
  28. lionagi/core/generic/node.py +14 -5
  29. lionagi/core/generic/tree_node.py +5 -1
  30. lionagi/core/mail/mail_manager.py +3 -1
  31. lionagi/core/mail/package.py +3 -1
  32. lionagi/core/message/action_request.py +9 -2
  33. lionagi/core/message/action_response.py +9 -3
  34. lionagi/core/message/instruction.py +8 -2
  35. lionagi/core/message/util.py +15 -5
  36. lionagi/core/report/base.py +12 -7
  37. lionagi/core/report/form.py +7 -4
  38. lionagi/core/report/report.py +10 -3
  39. lionagi/core/report/util.py +3 -1
  40. lionagi/core/rule/action.py +4 -1
  41. lionagi/core/rule/base.py +17 -6
  42. lionagi/core/rule/rulebook.py +8 -4
  43. lionagi/core/rule/string.py +3 -1
  44. lionagi/core/session/branch.py +15 -4
  45. lionagi/core/session/session.py +6 -2
  46. lionagi/core/unit/parallel_unit.py +9 -3
  47. lionagi/core/unit/template/action.py +1 -1
  48. lionagi/core/unit/template/predict.py +3 -1
  49. lionagi/core/unit/template/select.py +5 -3
  50. lionagi/core/unit/unit.py +4 -2
  51. lionagi/core/unit/unit_form.py +13 -15
  52. lionagi/core/unit/unit_mixin.py +45 -27
  53. lionagi/core/unit/util.py +7 -3
  54. lionagi/core/validator/validator.py +28 -15
  55. lionagi/core/work/work_edge.py +7 -3
  56. lionagi/core/work/work_task.py +11 -5
  57. lionagi/core/work/worker.py +20 -5
  58. lionagi/core/work/worker_engine.py +6 -2
  59. lionagi/core/work/worklog.py +3 -1
  60. lionagi/experimental/compressor/llm_compressor.py +20 -5
  61. lionagi/experimental/directive/README.md +1 -1
  62. lionagi/experimental/directive/parser/base_parser.py +41 -14
  63. lionagi/experimental/directive/parser/base_syntax.txt +23 -23
  64. lionagi/experimental/directive/template/base_template.py +14 -6
  65. lionagi/experimental/directive/tokenizer.py +3 -1
  66. lionagi/experimental/evaluator/README.md +1 -1
  67. lionagi/experimental/evaluator/ast_evaluator.py +6 -2
  68. lionagi/experimental/evaluator/base_evaluator.py +27 -16
  69. lionagi/integrations/bridge/autogen_/autogen_.py +7 -3
  70. lionagi/integrations/bridge/langchain_/documents.py +13 -10
  71. lionagi/integrations/bridge/llamaindex_/llama_pack.py +36 -12
  72. lionagi/integrations/bridge/llamaindex_/node_parser.py +8 -3
  73. lionagi/integrations/bridge/llamaindex_/reader.py +3 -1
  74. lionagi/integrations/bridge/llamaindex_/textnode.py +9 -3
  75. lionagi/integrations/bridge/pydantic_/pydantic_bridge.py +7 -1
  76. lionagi/integrations/bridge/transformers_/install_.py +3 -1
  77. lionagi/integrations/chunker/chunk.py +5 -2
  78. lionagi/integrations/loader/load.py +7 -3
  79. lionagi/integrations/loader/load_util.py +35 -16
  80. lionagi/integrations/provider/oai.py +13 -4
  81. lionagi/integrations/provider/openrouter.py +13 -4
  82. lionagi/integrations/provider/services.py +3 -1
  83. lionagi/integrations/provider/transformers.py +5 -3
  84. lionagi/integrations/storage/neo4j.py +23 -7
  85. lionagi/integrations/storage/storage_util.py +23 -7
  86. lionagi/integrations/storage/structure_excel.py +7 -2
  87. lionagi/integrations/storage/to_csv.py +8 -2
  88. lionagi/integrations/storage/to_excel.py +11 -3
  89. lionagi/libs/ln_api.py +41 -19
  90. lionagi/libs/ln_context.py +4 -4
  91. lionagi/libs/ln_convert.py +35 -14
  92. lionagi/libs/ln_dataframe.py +9 -3
  93. lionagi/libs/ln_func_call.py +53 -18
  94. lionagi/libs/ln_image.py +9 -5
  95. lionagi/libs/ln_knowledge_graph.py +21 -7
  96. lionagi/libs/ln_nested.py +57 -16
  97. lionagi/libs/ln_parse.py +45 -15
  98. lionagi/libs/ln_queue.py +8 -3
  99. lionagi/libs/ln_tokenize.py +19 -6
  100. lionagi/libs/ln_validate.py +14 -3
  101. lionagi/libs/sys_util.py +44 -12
  102. lionagi/lions/coder/coder.py +24 -8
  103. lionagi/lions/coder/util.py +6 -2
  104. lionagi/lions/researcher/data_source/google_.py +12 -4
  105. lionagi/lions/researcher/data_source/wiki_.py +3 -1
  106. lionagi/version.py +1 -1
  107. {lionagi-0.2.11.dist-info → lionagi-0.3.0.dist-info}/METADATA +6 -7
  108. lionagi-0.3.0.dist-info/RECORD +226 -0
  109. lionagi/tests/__init__.py +0 -0
  110. lionagi/tests/api/__init__.py +0 -0
  111. lionagi/tests/api/aws/__init__.py +0 -0
  112. lionagi/tests/api/aws/conftest.py +0 -25
  113. lionagi/tests/api/aws/test_aws_s3.py +0 -6
  114. lionagi/tests/integrations/__init__.py +0 -0
  115. lionagi/tests/libs/__init__.py +0 -0
  116. lionagi/tests/libs/test_api.py +0 -48
  117. lionagi/tests/libs/test_convert.py +0 -89
  118. lionagi/tests/libs/test_field_validators.py +0 -354
  119. lionagi/tests/libs/test_func_call.py +0 -701
  120. lionagi/tests/libs/test_nested.py +0 -382
  121. lionagi/tests/libs/test_parse.py +0 -171
  122. lionagi/tests/libs/test_queue.py +0 -68
  123. lionagi/tests/libs/test_sys_util.py +0 -222
  124. lionagi/tests/test_core/__init__.py +0 -0
  125. lionagi/tests/test_core/collections/__init__.py +0 -0
  126. lionagi/tests/test_core/collections/test_component.py +0 -208
  127. lionagi/tests/test_core/collections/test_exchange.py +0 -139
  128. lionagi/tests/test_core/collections/test_flow.py +0 -146
  129. lionagi/tests/test_core/collections/test_pile.py +0 -172
  130. lionagi/tests/test_core/collections/test_progression.py +0 -130
  131. lionagi/tests/test_core/generic/__init__.py +0 -0
  132. lionagi/tests/test_core/generic/test_edge.py +0 -69
  133. lionagi/tests/test_core/generic/test_graph.py +0 -97
  134. lionagi/tests/test_core/generic/test_node.py +0 -107
  135. lionagi/tests/test_core/generic/test_structure.py +0 -194
  136. lionagi/tests/test_core/generic/test_tree_node.py +0 -74
  137. lionagi/tests/test_core/graph/__init__.py +0 -0
  138. lionagi/tests/test_core/graph/test_graph.py +0 -71
  139. lionagi/tests/test_core/graph/test_tree.py +0 -76
  140. lionagi/tests/test_core/mail/__init__.py +0 -0
  141. lionagi/tests/test_core/mail/test_mail.py +0 -98
  142. lionagi/tests/test_core/test_branch.py +0 -116
  143. lionagi/tests/test_core/test_form.py +0 -47
  144. lionagi/tests/test_core/test_report.py +0 -106
  145. lionagi/tests/test_core/test_structure/__init__.py +0 -0
  146. lionagi/tests/test_core/test_structure/test_base_structure.py +0 -198
  147. lionagi/tests/test_core/test_structure/test_graph.py +0 -55
  148. lionagi/tests/test_core/test_structure/test_tree.py +0 -49
  149. lionagi/tests/test_core/test_validator.py +0 -112
  150. lionagi-0.2.11.dist-info/RECORD +0 -267
  151. {lionagi-0.2.11.dist-info → lionagi-0.3.0.dist-info}/LICENSE +0 -0
  152. {lionagi-0.2.11.dist-info → lionagi-0.3.0.dist-info}/WHEEL +0 -0
@@ -1,701 +0,0 @@
1
- import asyncio
2
- import time
3
- import unittest
4
-
5
- from lionagi.libs.ln_func_call import *
6
-
7
-
8
- class TestLCall(unittest.TestCase):
9
- def test_simple_function(self):
10
- result = lcall([1, 2, 3], lambda x: x * 2)
11
- self.assertEqual(result, [2, 4, 6])
12
-
13
- def test_function_returns_lists_flatten(self):
14
- result = lcall([1, 2], lambda x: [x, x], flatten=True)
15
- self.assertEqual(result, [1, 1, 2, 2])
16
-
17
- def test_function_returns_none_dropna(self):
18
- result = lcall([1, 2, None], lambda x: x if x else None, dropna=True)
19
- self.assertEqual(result, [1, 2])
20
-
21
- def test_function_with_kwargs(self):
22
- result = lcall([1, 2], lambda x, y: x + y, y=10)
23
- self.assertEqual(result, [11, 12])
24
-
25
- def test_empty_input(self):
26
- self.assertEqual(lcall([], lambda x: x * 2), [])
27
-
28
- def test_single_element(self):
29
- self.assertEqual(lcall(5, lambda x: x * 2), [10])
30
-
31
- def test_list_input_with_simple_function(self):
32
- self.assertEqual(lcall([1, 2, 3], lambda x: x * 2), [2, 4, 6])
33
-
34
- def test_flatten(self):
35
- self.assertEqual(
36
- lcall([[1, 2], [3, 4]], lambda x: x * 2, flatten=True), [2, 4, 6, 8]
37
- )
38
-
39
- def test_drop_none(self):
40
- self.assertEqual(lcall([1, None, 2], lambda x: x, dropna=True), [1, 2])
41
-
42
- def test_with_lambda_function(self):
43
- self.assertEqual(lcall([1, 2, 3], lambda x: x + 1), [2, 3, 4])
44
-
45
- def test_exception_handling(self):
46
- def faulty_func(x):
47
- raise ValueError("Faulty Function")
48
-
49
- with self.assertRaises(ValueError):
50
- lcall([1, 2, 3], faulty_func)
51
-
52
- def test_with_additional_arguments(self):
53
- self.assertEqual(lcall([1, 2, 3], lambda x, y: x + y, y=2), [3, 4, 5])
54
-
55
-
56
- class TestAlCall(unittest.IsolatedAsyncioTestCase):
57
-
58
- # async def test_empty_input(self):
59
- # result = await alcall([], lambda x: x * 2, flatten=True)
60
- # self.assertEqual(result, [])
61
-
62
- async def test_single_element_with_async_function(self):
63
- async def async_func(x):
64
- return x * 2
65
-
66
- result = await alcall(5, async_func)
67
- self.assertEqual(result, [10])
68
-
69
- async def test_list_input_with_sync_function(self):
70
- result = await alcall([1, 2, 3], lambda x: x * 2)
71
- self.assertEqual(result, [2, 4, 6])
72
-
73
- async def test_flatten_with_async_function(self):
74
- async def async_func(x):
75
- return x * 2
76
-
77
- result = await alcall([[1, 2], [3, 4]], async_func, flatten=True)
78
- self.assertEqual(result, [2, 4, 6, 8])
79
-
80
- async def test_drop_none_with_sync_function(self):
81
- result = await alcall([1, None, 2], lambda x: x, dropna=True)
82
- self.assertEqual(result, [1, 2])
83
-
84
- async def test_exception_handling_in_async_function(self):
85
- async def faulty_func(x):
86
- raise ValueError("Faulty Function")
87
-
88
- with self.assertRaises(ValueError):
89
- await alcall([1, 2, 3], faulty_func)
90
-
91
- async def test_with_additional_arguments(self):
92
- async def async_func(x, y):
93
- return x + y
94
-
95
- result = await alcall([1, 2, 3], async_func, y=2)
96
- self.assertEqual(result, [3, 4, 5])
97
-
98
-
99
- import asyncio
100
-
101
-
102
- class TestTCall(unittest.IsolatedAsyncioTestCase):
103
-
104
- async def test_sync_function_call(self):
105
- def sync_func(x):
106
- return x * 2
107
-
108
- result = await tcall(sync_func, 5)
109
- self.assertEqual(result, 10)
110
-
111
- async def test_async_function_call(self):
112
- async def async_func(x):
113
- return x * 2
114
-
115
- result = await tcall(async_func, 5)
116
- self.assertEqual(result, 10)
117
-
118
- async def test_with_delay(self):
119
- async def async_func(x):
120
- return x * 2
121
-
122
- start_time = asyncio.get_event_loop().time()
123
- await tcall(async_func, 5, delay=1)
124
- elapsed = asyncio.get_event_loop().time() - start_time
125
- self.assertTrue(elapsed >= 1)
126
-
127
- async def test_error_handling(self):
128
- async def async_func(x):
129
- raise ValueError("Test Error")
130
-
131
- with self.assertRaises(ValueError):
132
- await tcall(async_func, 5, err_msg="Custom Error Message")
133
-
134
- async def test_execution_timing(self):
135
- async def async_func(x):
136
- return x * 2
137
-
138
- result, duration = await tcall(async_func, 5, timing=True)
139
- self.assertEqual(result, 10)
140
- self.assertIsInstance(duration, float)
141
-
142
- async def test_ignore_error(self):
143
- def sync_func(x):
144
- raise ValueError("Test Error")
145
-
146
- result = await tcall(sync_func, 5, ignore_err=True)
147
- self.assertIsNone(result)
148
-
149
- async def test_with_additional_arguments(self):
150
- async def async_func(x, y):
151
- return x + y
152
-
153
- result = await tcall(async_func, 5, y=3)
154
- self.assertEqual(result, 8)
155
-
156
-
157
- class TestMCall(unittest.IsolatedAsyncioTestCase):
158
-
159
- async def test_single_function_single_input(self):
160
- async def async_func(x):
161
- return x * 2
162
-
163
- result = await mcall(5, async_func)
164
- self.assertEqual(result, [10])
165
-
166
- async def test_list_of_functions_single_input(self):
167
- async def async_func1(x):
168
- return x * 2
169
-
170
- async def async_func2(x):
171
- return x + 3
172
-
173
- with self.assertRaises(ValueError):
174
- await mcall([5], [async_func1, async_func2])
175
-
176
- async def test_single_function_list_of_inputs(self):
177
- async def async_func(x):
178
- return x * 2
179
-
180
- with self.assertRaises(ValueError):
181
- await mcall([1, 2, 3], async_func)
182
-
183
- async def test_list_of_functions_list_of_inputs_explode_false(self):
184
- async def async_func1(x):
185
- return x * 2
186
-
187
- async def async_func2(x):
188
- return x + 3
189
-
190
- result = await mcall([1, 2], [async_func1, async_func2])
191
- self.assertEqual(result, [2, 5])
192
-
193
- async def test_list_of_functions_list_of_inputs_explode_true(self):
194
- async def async_func1(x):
195
- return x * 2
196
-
197
- async def async_func2(x):
198
- return x + 3
199
-
200
- result = await mcall([1, 2], [async_func1, async_func2], explode=True)
201
- self.assertEqual(result, [[2, 4], [4, 5]])
202
-
203
- async def test_flatten_and_dropna(self):
204
- async def async_func1(x):
205
- return x * 2
206
-
207
- async def async_func2(x):
208
- return x + 3
209
-
210
- result = await mcall([[1, None], [None, 2]], [async_func1, async_func2])
211
- self.assertEqual(result, [2, 5])
212
-
213
- async def test_exception_handling(self):
214
- async def async_func(x):
215
- if x == 2:
216
- raise ValueError("Test Error")
217
- return x * 2
218
-
219
- with self.assertRaises(ValueError):
220
- await mcall([1, 2], [async_func, async_func])
221
-
222
- async def test_with_additional_arguments(self):
223
- async def async_func(x, y):
224
- return x + y
225
-
226
- result = await mcall([1, 2], [async_func, async_func], y=2)
227
- self.assertEqual(result, [3, 4])
228
-
229
-
230
- class TestBCall(unittest.IsolatedAsyncioTestCase):
231
-
232
- async def test_empty_input_list(self):
233
- async def async_func(x):
234
- return x * 2
235
-
236
- result = await bcall([], async_func, batch_size=2)
237
- self.assertEqual(result, [])
238
-
239
- async def test_sync_function_call(self):
240
- def sync_func(x):
241
- return x * 2
242
-
243
- result = await bcall([1, 2, 3], sync_func, batch_size=2)
244
- self.assertEqual(result, [2, 4, 6])
245
-
246
- async def test_async_function_call(self):
247
- async def async_func(x):
248
- return x * 2
249
-
250
- result = await bcall([1, 2, 3], async_func, batch_size=2)
251
- self.assertEqual(result, [2, 4, 6])
252
-
253
- async def test_batch_processing(self):
254
- async def async_func(x):
255
- return x * 2
256
-
257
- # Test with 4 elements and a batch size of 2
258
- result = await bcall([1, 2, 3, 4], async_func, batch_size=2)
259
- self.assertEqual(result, [2, 4, 6, 8])
260
-
261
- async def test_exception_handling(self):
262
- async def async_func(x):
263
- if x == 2:
264
- raise ValueError("Test Error")
265
- return x * 2
266
-
267
- with self.assertRaises(ValueError):
268
- await bcall([1, 2, 3], async_func, batch_size=2)
269
-
270
- async def test_with_additional_arguments(self):
271
- async def async_func(x, y):
272
- return x + y
273
-
274
- result = await bcall([1, 2, 3], async_func, batch_size=2, y=3)
275
- self.assertEqual(result, [4, 5, 6])
276
-
277
-
278
- class TestRCall(unittest.IsolatedAsyncioTestCase):
279
-
280
- async def test_sync_function_without_timeout(self):
281
- def sync_func(x):
282
- return x * 2
283
-
284
- result = await rcall(sync_func, 5)
285
- self.assertEqual(result, 10)
286
-
287
- async def test_async_function_without_timeout(self):
288
- async def async_func(x):
289
- return x * 2
290
-
291
- result = await rcall(async_func, 5)
292
- self.assertEqual(result, 10)
293
-
294
- async def test_timeout(self):
295
- async def async_func(x):
296
- await asyncio.sleep(2)
297
- return x
298
-
299
- with self.assertRaises(RuntimeError):
300
- await rcall(async_func, 5, timeout=0.1)
301
-
302
- async def test_retry_mechanism(self):
303
- attempt_count = 0
304
-
305
- def sync_func(x):
306
- nonlocal attempt_count
307
- attempt_count += 1
308
- raise ValueError("Test Error")
309
-
310
- with self.assertRaises(RuntimeError):
311
- await rcall(sync_func, 5, retries=3, delay=0.1, backoff_factor=1.1)
312
- self.assertEqual(attempt_count, 3) # Initial call + 3 retries
313
-
314
- async def test_default_value_on_exception(self):
315
- def sync_func(x):
316
- raise ValueError("Test Error")
317
-
318
- result = await rcall(sync_func, 5, default=10, delay=0)
319
- self.assertEqual(result, 10)
320
-
321
- async def test_exception_propagation(self):
322
- def sync_func(x):
323
- raise ValueError("Test Error")
324
-
325
- with self.assertRaises(RuntimeError):
326
- await rcall(sync_func, 5)
327
-
328
-
329
- class TestCallDecorator(unittest.IsolatedAsyncioTestCase):
330
-
331
- def test_cache_decorator_sync(self):
332
- @CallDecorator.cache
333
- def sync_func(x):
334
- return x * 2
335
-
336
- first_call = sync_func(5)
337
- second_call = sync_func(5)
338
-
339
- self.assertEqual(first_call, 10)
340
- self.assertIs(first_call, second_call)
341
-
342
- async def test_cache_decorator_async(self):
343
- @CallDecorator.cache
344
- async def async_func(x):
345
- return x * 2
346
-
347
- first_call = await async_func(5)
348
- second_call = await async_func(5)
349
-
350
- self.assertEqual(first_call, 10)
351
- self.assertIs(first_call, second_call)
352
-
353
- def test_throttling_behavior(self):
354
- @CallDecorator.throttle(period=2) # 2 seconds throttle period
355
- def test_func():
356
- return time.time()
357
-
358
- start_time = time.time()
359
- first_call = test_func()
360
- second_call = test_func()
361
- end_time = time.time()
362
-
363
- self.assertLess(first_call - start_time, 1) # First call should be immediate
364
- self.assertGreaterEqual(
365
- second_call - first_call, 2
366
- ) # Second call should be delayed
367
- self.assertLessEqual(
368
- end_time - start_time, 3
369
- ) # Total time should be within reasonable bounds
370
-
371
- def test_successive_calls_outside_throttle_period(self):
372
- @CallDecorator.throttle(period=1) # 1 second throttle period
373
- def test_func():
374
- return time.time()
375
-
376
- first_call = test_func()
377
- time.sleep(1.1) # Sleep to exceed the throttle period
378
- second_call = test_func()
379
-
380
- self.assertGreaterEqual(
381
- second_call - first_call, 1
382
- ) # Second call should not be delayed
383
-
384
- def test_throttling_with_different_inputs(self):
385
- @CallDecorator.throttle(period=2) # 2 seconds throttle period
386
- def test_func(x):
387
- return x
388
-
389
- first_call = test_func(1)
390
- time.sleep(0.5)
391
- second_call = test_func(2)
392
-
393
- self.assertEqual(first_call, 1)
394
- self.assertEqual(
395
- second_call, 2
396
- ) # Should return the result of the first call due to throttling
397
-
398
- def test_preprocessing(self):
399
- def preprocess(x):
400
- return x * 2
401
-
402
- @CallDecorator.pre_post_process(preprocess, lambda x: x)
403
- def test_func(x):
404
- return x + 3
405
-
406
- result = test_func(2) # Preprocess: 2 * 2 -> 4, Func: 4 + 3
407
- self.assertEqual(result, 7)
408
-
409
- def test_postprocessing(self):
410
- def postprocess(x):
411
- return x * 3
412
-
413
- @CallDecorator.pre_post_process(lambda x: x, postprocess)
414
- def test_func(x):
415
- return x + 2
416
-
417
- result = test_func(2) # Func: 2 + 2 -> 4, Postprocess: 4 * 3
418
- self.assertEqual(result, 12)
419
-
420
- def test_preprocessing_and_postprocessing(self):
421
- def preprocess(x):
422
- return x * 2
423
-
424
- def postprocess(x):
425
- return x * 3
426
-
427
- @CallDecorator.pre_post_process(preprocess, postprocess)
428
- def test_func(x):
429
- return x + 1
430
-
431
- result = test_func(
432
- 2
433
- ) # Preprocess: 2 * 2 -> 4, Func: 4 + 1 -> 5, Postprocess: 5 * 3
434
- self.assertEqual(result, 15)
435
-
436
- def test_filtering_based_on_predicate(self):
437
- is_even = lambda x: x % 2 == 0
438
-
439
- @CallDecorator.filter(is_even)
440
- def test_func():
441
- return [1, 2, 3, 4, 5]
442
-
443
- result = test_func()
444
- self.assertEqual(result, [2, 4])
445
-
446
- def test_no_items_satisfy_predicate(self):
447
- is_negative = lambda x: x < 0
448
-
449
- @CallDecorator.filter(is_negative)
450
- def test_func():
451
- return [1, 2, 3, 4, 5]
452
-
453
- result = test_func()
454
- self.assertEqual(result, [])
455
-
456
- def test_all_items_satisfy_predicate(self):
457
- is_positive = lambda x: x > 0
458
-
459
- @CallDecorator.filter(is_positive)
460
- def test_func():
461
- return [1, 2, 3, 4, 5]
462
-
463
- result = test_func()
464
- self.assertEqual(result, [1, 2, 3, 4, 5])
465
-
466
- def test_mapping_function(self):
467
- double = lambda x: x * 2
468
-
469
- @CallDecorator.map(double)
470
- def test_func():
471
- return [1, 2, 3, 4, 5]
472
-
473
- result = test_func()
474
- self.assertEqual(result, [2, 4, 6, 8, 10])
475
-
476
- def test_empty_list(self):
477
- double = lambda x: x * 2
478
-
479
- @CallDecorator.map(double)
480
- def test_func():
481
- return []
482
-
483
- result = test_func()
484
- self.assertEqual(result, [])
485
-
486
- def test_mapping_with_different_data_types(self):
487
- to_string = lambda x: str(x)
488
-
489
- @CallDecorator.map(to_string)
490
- def test_func():
491
- return [1, 2.5, "test", True]
492
-
493
- result = test_func()
494
- self.assertEqual(result, ["1", "2.5", "test", "True"])
495
-
496
- def test_reduction_function(self):
497
- sum_func = lambda x, y: x + y
498
-
499
- @CallDecorator.reduce(sum_func, 0)
500
- def test_func():
501
- return [1, 2, 3, 4, 5]
502
-
503
- result = test_func()
504
- self.assertEqual(result, 15) # Sum of 1, 2, 3, 4, 5
505
-
506
- def test_empty_list(self):
507
- sum_func = lambda x, y: x + y
508
-
509
- @CallDecorator.reduce(sum_func, 0)
510
- def test_func():
511
- return []
512
-
513
- result = test_func()
514
- self.assertEqual(result, 0) # Initial value
515
-
516
- def test_reduction_with_initial_value(self):
517
- sum_func = lambda x, y: x + y
518
-
519
- @CallDecorator.reduce(sum_func, 10)
520
- def test_func():
521
- return [1, 2, 3, 4, 5]
522
-
523
- result = test_func()
524
- self.assertEqual(result, 25) # 10 (initial) + Sum of 1, 2, 3, 4, 5
525
-
526
- def test_function_composition(self):
527
- double = lambda x: x * 2
528
- add_five = lambda x: x + 5
529
-
530
- @CallDecorator.compose(add_five, double) # First add_five, then double
531
- def test_func():
532
- return 3
533
-
534
- result = test_func()
535
- self.assertEqual(result, (3 + 5) * 2)
536
-
537
- def test_empty_function_list(self):
538
- @CallDecorator.compose()
539
- def test_func():
540
- return "test"
541
-
542
- result = test_func()
543
- self.assertEqual(result, "test")
544
-
545
- def test_error_handling_in_composition(self):
546
- def raise_error(x):
547
- raise ValueError("Error in function")
548
-
549
- @CallDecorator.compose(raise_error)
550
- def test_func():
551
- return 3
552
-
553
- with self.assertRaises(ValueError):
554
- test_func()
555
-
556
- async def test_concurrency_limit(self):
557
- current_concurrency = 0
558
- max_concurrent_calls = 0
559
-
560
- @CallDecorator.max_concurrency(limit=2)
561
- async def test_func():
562
- nonlocal current_concurrency, max_concurrent_calls
563
- current_concurrency += 1
564
- max_concurrent_calls = max(max_concurrent_calls, current_concurrency)
565
- await asyncio.sleep(0.1) # Simulate async work
566
- current_concurrency -= 1
567
-
568
- await asyncio.gather(*(test_func() for _ in range(5)))
569
- self.assertEqual(max_concurrent_calls, 2)
570
-
571
- async def test_function_completing_within_limit(self):
572
- @CallDecorator.max_concurrency(limit=2)
573
- async def test_func(x):
574
- await asyncio.sleep(0.1)
575
- return x
576
-
577
- results = await asyncio.gather(*(test_func(i) for i in range(5)))
578
- self.assertEqual(results, [0, 1, 2, 3, 4])
579
-
580
-
581
- class TestThrottleClass(unittest.TestCase):
582
-
583
- def test_throttling_behavior_sync(self):
584
- throttle_decorator = Throttle(0.1) # 2 seconds throttle period
585
-
586
- @throttle_decorator
587
- def test_func():
588
- return time.time()
589
-
590
- first_call_time = test_func()
591
- time.sleep(0.1) # Sleep less than the throttle period
592
- second_call_time = test_func()
593
-
594
- self.assertGreaterEqual(
595
- second_call_time - first_call_time, 0.1
596
- ) # Second call should be throttled
597
-
598
- def test_throttling_behavior_async(self):
599
- throttle_decorator = Throttle(1) # 2 seconds throttle period
600
-
601
- @throttle_decorator
602
- async def test_func():
603
- return time.time()
604
-
605
- async def async_test():
606
- first_call_time = await test_func()
607
- await asyncio.sleep(0.1) # Sleep less than the throttle period
608
- second_call_time = await test_func()
609
-
610
- self.assertGreaterEqual(
611
- second_call_time - first_call_time, 1
612
- ) # Second call should be throttled
613
-
614
- asyncio.run(async_test())
615
-
616
- def test_successive_calls_with_sufficient_delay(self):
617
- throttle_decorator = Throttle(0.1) # 1 second throttle period
618
-
619
- @throttle_decorator
620
- def test_func():
621
- return time.time()
622
-
623
- first_call_time = test_func()
624
- time.sleep(0.12) # Sleep more than the throttle period
625
- second_call_time = test_func()
626
-
627
- self.assertLess(
628
- second_call_time - first_call_time, 0.2
629
- ) # Second call should not be throttled
630
-
631
-
632
- class TestAsyncRetryDecorator(unittest.IsolatedAsyncioTestCase):
633
- async def test_successful_retry(self):
634
- attempt = 0
635
-
636
- @CallDecorator.retry(retries=3, delay=0.1, backoff_factor=2)
637
- async def test_func():
638
- nonlocal attempt
639
- attempt += 1
640
- if attempt < 3:
641
- raise ValueError("Test failure")
642
- return "Success"
643
-
644
- result = await test_func()
645
- self.assertEqual(result, "Success")
646
- self.assertEqual(attempt, 3)
647
-
648
- async def test_retry_limit(self):
649
- attempt = 0
650
-
651
- @CallDecorator.retry(retries=2, delay=0.1, backoff_factor=2)
652
- async def test_func():
653
- nonlocal attempt
654
- attempt += 1
655
- raise ValueError("Test failure")
656
-
657
- try:
658
- await test_func()
659
- except:
660
- pass
661
- self.assertEqual(attempt, 2)
662
-
663
- async def test_immediate_success(self):
664
- attempt = 0
665
-
666
- @CallDecorator.retry(retries=3, delay=0.1, backoff_factor=2)
667
- async def test_func():
668
- nonlocal attempt
669
- attempt += 1
670
- return "Success"
671
-
672
- result = await test_func()
673
- self.assertEqual(result, "Success")
674
- self.assertEqual(attempt, 1)
675
-
676
- async def test_retry_with_delays(self):
677
- attempt = 0
678
- start_time = time.time()
679
-
680
- @CallDecorator.retry(retries=3, delay=0.1, backoff_factor=2)
681
- async def test_func():
682
- nonlocal attempt
683
- attempt += 1
684
- if attempt < 3:
685
- raise ValueError("Test failure")
686
- return "Success"
687
-
688
- result = await test_func()
689
- end_time = time.time()
690
- elapsed_time = end_time - start_time
691
-
692
- self.assertEqual(result, "Success")
693
- # Note: The actual delay might be slightly longer than expected due to asyncio's event loop scheduling.
694
- self.assertTrue(
695
- elapsed_time >= 0.3, f"Elapsed time was {elapsed_time}, expected >= 3"
696
- )
697
- self.assertEqual(attempt, 3)
698
-
699
-
700
- if __name__ == "__main__":
701
- unittest.main()