lionagi 0.0.305__py3-none-any.whl → 0.0.307__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- lionagi/__init__.py +2 -5
- lionagi/core/__init__.py +7 -4
- lionagi/core/agent/__init__.py +3 -0
- lionagi/core/agent/base_agent.py +46 -0
- lionagi/core/branch/__init__.py +4 -0
- lionagi/core/branch/base/__init__.py +0 -0
- lionagi/core/branch/base_branch.py +100 -78
- lionagi/core/branch/branch.py +22 -34
- lionagi/core/branch/branch_flow_mixin.py +3 -7
- lionagi/core/branch/executable_branch.py +192 -0
- lionagi/core/branch/util.py +77 -162
- lionagi/core/direct/__init__.py +13 -0
- lionagi/core/direct/parallel_predict.py +127 -0
- lionagi/core/direct/parallel_react.py +0 -0
- lionagi/core/direct/parallel_score.py +0 -0
- lionagi/core/direct/parallel_select.py +0 -0
- lionagi/core/direct/parallel_sentiment.py +0 -0
- lionagi/core/direct/predict.py +174 -0
- lionagi/core/direct/react.py +33 -0
- lionagi/core/direct/score.py +163 -0
- lionagi/core/direct/select.py +144 -0
- lionagi/core/direct/sentiment.py +51 -0
- lionagi/core/direct/utils.py +83 -0
- lionagi/core/flow/__init__.py +0 -3
- lionagi/core/flow/monoflow/{mono_react.py → ReAct.py} +52 -9
- lionagi/core/flow/monoflow/__init__.py +9 -0
- lionagi/core/flow/monoflow/{mono_chat.py → chat.py} +11 -11
- lionagi/core/flow/monoflow/{mono_chat_mixin.py → chat_mixin.py} +33 -27
- lionagi/core/flow/monoflow/{mono_followup.py → followup.py} +7 -6
- lionagi/core/flow/polyflow/__init__.py +1 -0
- lionagi/core/flow/polyflow/{polychat.py → chat.py} +15 -3
- lionagi/core/mail/__init__.py +8 -0
- lionagi/core/mail/mail_manager.py +88 -40
- lionagi/core/mail/schema.py +32 -6
- lionagi/core/messages/__init__.py +3 -0
- lionagi/core/messages/schema.py +56 -25
- lionagi/core/prompt/__init__.py +0 -0
- lionagi/core/prompt/prompt_template.py +0 -0
- lionagi/core/schema/__init__.py +7 -5
- lionagi/core/schema/action_node.py +29 -0
- lionagi/core/schema/base_mixin.py +56 -59
- lionagi/core/schema/base_node.py +35 -38
- lionagi/core/schema/condition.py +24 -0
- lionagi/core/schema/data_logger.py +98 -98
- lionagi/core/schema/data_node.py +19 -19
- lionagi/core/schema/prompt_template.py +0 -0
- lionagi/core/schema/structure.py +293 -190
- lionagi/core/session/__init__.py +1 -3
- lionagi/core/session/session.py +196 -214
- lionagi/core/tool/tool_manager.py +95 -103
- lionagi/integrations/__init__.py +1 -3
- lionagi/integrations/bridge/langchain_/documents.py +17 -18
- lionagi/integrations/bridge/langchain_/langchain_bridge.py +14 -14
- lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +22 -22
- lionagi/integrations/bridge/llamaindex_/node_parser.py +12 -12
- lionagi/integrations/bridge/llamaindex_/reader.py +11 -11
- lionagi/integrations/bridge/llamaindex_/textnode.py +7 -7
- lionagi/integrations/config/openrouter_configs.py +0 -1
- lionagi/integrations/provider/oai.py +26 -26
- lionagi/integrations/provider/services.py +38 -38
- lionagi/libs/__init__.py +34 -1
- lionagi/libs/ln_api.py +211 -221
- lionagi/libs/ln_async.py +53 -60
- lionagi/libs/ln_convert.py +118 -120
- lionagi/libs/ln_dataframe.py +32 -33
- lionagi/libs/ln_func_call.py +334 -342
- lionagi/libs/ln_nested.py +99 -107
- lionagi/libs/ln_parse.py +175 -158
- lionagi/libs/sys_util.py +52 -52
- lionagi/tests/test_core/test_base_branch.py +427 -427
- lionagi/tests/test_core/test_branch.py +292 -292
- lionagi/tests/test_core/test_mail_manager.py +57 -57
- lionagi/tests/test_core/test_session.py +254 -266
- lionagi/tests/test_core/test_session_base_util.py +299 -300
- lionagi/tests/test_core/test_tool_manager.py +70 -74
- lionagi/tests/test_libs/test_nested.py +2 -7
- lionagi/tests/test_libs/test_parse.py +2 -2
- lionagi/version.py +1 -1
- {lionagi-0.0.305.dist-info → lionagi-0.0.307.dist-info}/METADATA +4 -2
- lionagi-0.0.307.dist-info/RECORD +115 -0
- lionagi-0.0.305.dist-info/RECORD +0 -94
- {lionagi-0.0.305.dist-info → lionagi-0.0.307.dist-info}/LICENSE +0 -0
- {lionagi-0.0.305.dist-info → lionagi-0.0.307.dist-info}/WHEEL +0 -0
- {lionagi-0.0.305.dist-info → lionagi-0.0.307.dist-info}/top_level.txt +0 -0
lionagi/libs/ln_func_call.py
CHANGED
@@ -36,33 +36,33 @@ def lcall(
|
|
36
36
|
function can be passed dynamically, allowing for flexible function application.
|
37
37
|
|
38
38
|
Args:
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
39
|
+
input_ (Any):
|
40
|
+
The input list or iterable to process. each element will be passed to the
|
41
|
+
provided `func` Callable.
|
42
|
+
func (Callable):
|
43
|
+
The function to apply to each element of `input_`. this function can be any
|
44
|
+
Callable that accepts the elements of `input_` as arguments.
|
45
|
+
flatten (bool, optional):
|
46
|
+
If True, the resulting list is flattened. useful when `func` returns a list.
|
47
|
+
defaults to False.
|
48
|
+
dropna (bool, optional):
|
49
|
+
If True, None values are removed from the final list. defaults to False.
|
50
|
+
**kwargs:
|
51
|
+
Additional keyword arguments to be passed to `func`.
|
52
52
|
|
53
53
|
Returns:
|
54
|
-
|
55
|
-
|
56
|
-
|
54
|
+
list[Any]:
|
55
|
+
The list of results after applying `func` to each input element, modified
|
56
|
+
according to `flatten` and `dropna` options.
|
57
57
|
|
58
58
|
Examples:
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
Apply a doubling function to each element:
|
60
|
+
>>> lcall([1, 2, 3], lambda x: x * 2)
|
61
|
+
[2, 4, 6]
|
62
62
|
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
apply a function that returns lists, then flatten the result:
|
64
|
+
>>> lcall([1, 2, None], lambda x: [x, x] if x else x, flatten=True, dropna=True)
|
65
|
+
[1, 1, 2, 2]
|
66
66
|
"""
|
67
67
|
lst = to_list(input_, dropna=dropna)
|
68
68
|
if len(to_list(func)) != 1:
|
@@ -91,26 +91,26 @@ async def alcall(
|
|
91
91
|
efficiency and overall execution time.
|
92
92
|
|
93
93
|
Args:
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
94
|
+
input_ (Any, optional):
|
95
|
+
The input to process. defaults to None, which requires `func` to be capable of
|
96
|
+
handling the absence of explicit input.
|
97
|
+
func (Callable, optional):
|
98
|
+
The asynchronous function to apply. defaults to None.
|
99
|
+
flatten (bool, optional):
|
100
|
+
Whether to flatten the result. useful when `func` returns a list or iterable
|
101
|
+
that should be merged into a single list. defaults to False.
|
102
|
+
**kwargs:
|
103
|
+
Keyword arguments to pass to the function.
|
104
104
|
|
105
105
|
Returns:
|
106
|
-
|
107
|
-
|
108
|
-
|
106
|
+
list[Any]:
|
107
|
+
A list of results after asynchronously applying the function to each element
|
108
|
+
of the input, potentially flattened.
|
109
109
|
|
110
110
|
examples:
|
111
|
-
|
112
|
-
|
113
|
-
|
111
|
+
>>> async def square(x): return x * x
|
112
|
+
>>> await alcall([1, 2, 3], square)
|
113
|
+
[1, 4, 9]
|
114
114
|
"""
|
115
115
|
tasks = []
|
116
116
|
if input_ is not None:
|
@@ -123,7 +123,7 @@ async def alcall(
|
|
123
123
|
outs = await AsyncUtil.execute_tasks(*tasks)
|
124
124
|
outs_ = []
|
125
125
|
for i in outs:
|
126
|
-
outs_.append(i if
|
126
|
+
outs_.append(await i if isinstance(i, (Coroutine, asyncio.Future)) else i)
|
127
127
|
|
128
128
|
return to_list(outs_, flatten=flatten, dropna=dropna)
|
129
129
|
|
@@ -135,23 +135,23 @@ async def mcall(
|
|
135
135
|
asynchronously map a function or functions over an input_ or inputs.
|
136
136
|
|
137
137
|
Args:
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
138
|
+
input_ (Any):
|
139
|
+
The input_ or inputs to process.
|
140
|
+
func (Any):
|
141
|
+
The function or functions to apply.
|
142
|
+
explode (bool, optional):
|
143
|
+
Whether to apply each function to each input_. default is False.
|
144
|
+
**kwargs:
|
145
|
+
Keyword arguments to pass to the function.
|
146
146
|
|
147
147
|
Returns:
|
148
|
-
|
148
|
+
list[Any]: A list of results after applying the function(s).
|
149
149
|
|
150
150
|
examples:
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
151
|
+
>>> async def add_one(x):
|
152
|
+
>>> return x + 1
|
153
|
+
>>> asyncio.run(mcall([1, 2, 3], add_one))
|
154
|
+
[2, 3, 4]
|
155
155
|
|
156
156
|
"""
|
157
157
|
inputs_ = to_list(input_, dropna=True)
|
@@ -159,17 +159,16 @@ async def mcall(
|
|
159
159
|
|
160
160
|
if explode:
|
161
161
|
tasks = [_alcall(inputs_, f, flatten=True, **kwargs) for f in funcs_]
|
162
|
-
|
163
|
-
else:
|
164
|
-
if len(inputs_) != len(funcs_):
|
165
|
-
raise ValueError(
|
166
|
-
"Inputs and functions must be the same length for map calling."
|
167
|
-
)
|
162
|
+
elif len(inputs_) == len(funcs_):
|
168
163
|
tasks = [
|
169
164
|
AsyncUtil.handle_async_sync(func, inp, **kwargs)
|
170
165
|
for inp, func in zip(inputs_, funcs_)
|
171
166
|
]
|
172
|
-
|
167
|
+
else:
|
168
|
+
raise ValueError(
|
169
|
+
"Inputs and functions must be the same length for map calling."
|
170
|
+
)
|
171
|
+
return await AsyncUtil.execute_tasks(*tasks)
|
173
172
|
|
174
173
|
|
175
174
|
async def bcall(
|
@@ -179,18 +178,18 @@ async def bcall(
|
|
179
178
|
asynchronously call a function on batches of inputs.
|
180
179
|
|
181
180
|
Args:
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
181
|
+
input_ (Any): The input_ to process.
|
182
|
+
func (Callable): The function to apply.
|
183
|
+
batch_size (int): The size of each batch.
|
184
|
+
**kwargs: Keyword arguments to pass to the function.
|
186
185
|
|
187
186
|
Returns:
|
188
|
-
|
187
|
+
list[Any]: A list of results after applying the function in batches.
|
189
188
|
|
190
189
|
examples:
|
191
|
-
|
192
|
-
|
193
|
-
|
190
|
+
>>> async def sum_batch(batch_): return sum(batch_)
|
191
|
+
>>> asyncio.run(bcall([1, 2, 3, 4], sum_batch, batch_size=2))
|
192
|
+
[3, 7]
|
194
193
|
"""
|
195
194
|
results = []
|
196
195
|
input_ = to_list(input_)
|
@@ -222,36 +221,36 @@ async def tcall(
|
|
222
221
|
frame, or when monitoring execution duration.
|
223
222
|
|
224
223
|
Args:
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
224
|
+
func (Callable):
|
225
|
+
The asynchronous function to be called.
|
226
|
+
*args:
|
227
|
+
Positional arguments to pass to the function.
|
228
|
+
delay (float, optional):
|
229
|
+
Time in seconds to wait before executing the function. default to 0.
|
230
|
+
err_msg (str | None, optional):
|
231
|
+
Custom error message to display if an error occurs. defaults to None.
|
232
|
+
ignore_err (bool, optional):
|
233
|
+
If True, suppresses any errors that occur during function execution,
|
234
|
+
optionally returning a default value. defaults to False.
|
235
|
+
timing (bool, optional):
|
236
|
+
If True, returns a tuple containing the result of the function and the
|
237
|
+
execution duration in seconds. defaults to False.
|
238
|
+
timeout (float | None, optional):
|
239
|
+
Maximum time in seconds allowed for the function execution. if the execution
|
240
|
+
exceeds this time, a timeout error is raised. defaults to None.
|
241
|
+
**kwargs:
|
242
|
+
Keyword arguments to pass to the function.
|
244
243
|
|
245
244
|
Returns:
|
246
|
-
|
247
|
-
|
248
|
-
|
245
|
+
Any:
|
246
|
+
The result of the function call. if `timing` is True, returns a tuple of
|
247
|
+
(result, execution duration).
|
249
248
|
|
250
249
|
examples:
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
250
|
+
>>> async def sample_function(x):
|
251
|
+
... return x * x
|
252
|
+
>>> await tcall(sample_function, 3, delay=1, timing=True)
|
253
|
+
(9, execution_duration)
|
255
254
|
"""
|
256
255
|
|
257
256
|
async def async_call() -> tuple[Any, float]:
|
@@ -284,10 +283,7 @@ async def tcall(
|
|
284
283
|
if not ignore_err:
|
285
284
|
raise
|
286
285
|
|
287
|
-
if AsyncUtil.is_coroutine_func(func)
|
288
|
-
return await async_call()
|
289
|
-
else:
|
290
|
-
return sync_call()
|
286
|
+
return await async_call() if AsyncUtil.is_coroutine_func(func) else sync_call()
|
291
287
|
|
292
288
|
|
293
289
|
async def rcall(
|
@@ -310,44 +306,42 @@ async def rcall(
|
|
310
306
|
persistent failures, and a backoff factor to control the delay increase.
|
311
307
|
|
312
308
|
Args:
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
309
|
+
func (Callable):
|
310
|
+
The asynchronous function to retry.
|
311
|
+
*args:
|
312
|
+
Positional arguments for the function.
|
313
|
+
retries (int, optional):
|
314
|
+
The number of retry attempts before giving up. default to 0.
|
315
|
+
delay (float, optional):
|
316
|
+
Initial delay between retries in seconds. default to 1.0.
|
317
|
+
backoff_factor (float, optional):
|
318
|
+
Multiplier for the delay between retries, for exponential backoff.
|
319
|
+
default to 2.0.
|
320
|
+
default (Any, optional):
|
321
|
+
A value to return if all retries fail. defaults to None.
|
322
|
+
timeout (float | None, optional):
|
323
|
+
Maximum duration in seconds for each attempt. defaults to None.
|
324
|
+
**kwargs:
|
325
|
+
Keyword arguments for the function.
|
330
326
|
|
331
327
|
Returns:
|
332
|
-
|
333
|
-
|
334
|
-
|
328
|
+
Any:
|
329
|
+
The result of the function call if successful within the retry attempts,
|
330
|
+
otherwise the `default` value if specified.
|
335
331
|
|
336
332
|
examples:
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
333
|
+
>>> async def fetch_data():
|
334
|
+
... # Simulate a fetch operation that might fail
|
335
|
+
... raise Exception("temporary error")
|
336
|
+
>>> await rcall(fetch_data, retries=3, delay=2, default="default value")
|
337
|
+
'default value'
|
342
338
|
"""
|
343
339
|
last_exception = None
|
344
340
|
result = None
|
345
341
|
|
346
342
|
for attempt in range(retries + 1) if retries == 0 else range(retries):
|
347
343
|
try:
|
348
|
-
|
349
|
-
result = await _tcall(func, *args, timeout=timeout, **kwargs)
|
350
|
-
return result
|
344
|
+
return await _tcall(func, *args, timeout=timeout, **kwargs)
|
351
345
|
except Exception as e:
|
352
346
|
last_exception = e
|
353
347
|
if attempt < retries:
|
@@ -368,14 +362,14 @@ def _dropna(lst_: list[Any]) -> list[Any]:
|
|
368
362
|
Remove None values from a list.
|
369
363
|
|
370
364
|
Args:
|
371
|
-
|
365
|
+
lst_ (list[Any]): A list potentially containing None values.
|
372
366
|
|
373
367
|
Returns:
|
374
|
-
|
368
|
+
list[Any]: A list with None values removed.
|
375
369
|
|
376
370
|
Examples:
|
377
|
-
|
378
|
-
|
371
|
+
>>> _dropna([1, None, 3, None])
|
372
|
+
[1, 3]
|
379
373
|
"""
|
380
374
|
return [item for item in lst_ if item is not None]
|
381
375
|
|
@@ -387,18 +381,18 @@ async def _alcall(
|
|
387
381
|
asynchronously apply a function to each element in the input_.
|
388
382
|
|
389
383
|
Args:
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
384
|
+
input (Any): The input_ to process.
|
385
|
+
func (Callable): The function to apply.
|
386
|
+
flatten (bool, optional): Whether to flatten the result. default is False.
|
387
|
+
**kwargs: Keyword arguments to pass to the function.
|
394
388
|
|
395
389
|
Returns:
|
396
|
-
|
390
|
+
list[Any]: A list of results after asynchronously applying the function.
|
397
391
|
|
398
392
|
examples:
|
399
|
-
|
400
|
-
|
401
|
-
|
393
|
+
>>> async def square(x): return x * x
|
394
|
+
>>> asyncio.run(alcall([1, 2, 3], square))
|
395
|
+
[1, 4, 9]
|
402
396
|
"""
|
403
397
|
lst = to_list(input_)
|
404
398
|
tasks = [AsyncUtil.handle_async_sync(func, i, **kwargs) for i in lst]
|
@@ -421,23 +415,23 @@ async def _tcall(
|
|
421
415
|
asynchronously call a function with optional delay, timeout, and error handling.
|
422
416
|
|
423
417
|
Args:
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
418
|
+
func (Callable): The function to call.
|
419
|
+
*args: Positional arguments to pass to the function.
|
420
|
+
delay (float): Delay before calling the function, in seconds.
|
421
|
+
err_msg (str | None): Custom error message.
|
422
|
+
ignore_err (bool): If True, ignore errors and return default.
|
423
|
+
timing (bool): If True, return a tuple (result, duration).
|
424
|
+
default (Any): Default value to return on error.
|
425
|
+
timeout (float | None): Timeout for the function call, in seconds.
|
426
|
+
**kwargs: Keyword arguments to pass to the function.
|
433
427
|
|
434
428
|
Returns:
|
435
|
-
|
429
|
+
Any: The result of the function call, or (result, duration) if timing is True.
|
436
430
|
|
437
431
|
examples:
|
438
|
-
|
439
|
-
|
440
|
-
|
432
|
+
>>> async def example_func(x): return x
|
433
|
+
>>> asyncio.run(tcall(example_func, 5, timing=True))
|
434
|
+
(5, duration)
|
441
435
|
"""
|
442
436
|
start_time = SysUtil.get_now(datetime_=False)
|
443
437
|
try:
|
@@ -515,21 +509,21 @@ class CallDecorator:
|
|
515
509
|
asyncio.TimeoutError if the execution time exceeds the specified timeout.
|
516
510
|
|
517
511
|
Args:
|
518
|
-
|
519
|
-
|
512
|
+
timeout (int):
|
513
|
+
The maximum duration, in seconds, that the function is allowed to execute.
|
520
514
|
|
521
515
|
Returns:
|
522
|
-
|
523
|
-
|
516
|
+
Callable:
|
517
|
+
A decorated function that enforces the specified execution timeout.
|
524
518
|
|
525
519
|
Examples:
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
520
|
+
>>> @CallDecorator.timeout(5)
|
521
|
+
... async def long_running_task():
|
522
|
+
... # Implementation that may exceed the timeout duration
|
523
|
+
... await asyncio.sleep(10)
|
524
|
+
... return "Completed"
|
525
|
+
... # Executing `long_running_task` will raise an asyncio.TimeoutError after 5
|
526
|
+
... # seconds
|
533
527
|
"""
|
534
528
|
|
535
529
|
def decorator(func: Callable[..., Any]) -> Callable:
|
@@ -556,26 +550,26 @@ class CallDecorator:
|
|
556
550
|
mechanisms.
|
557
551
|
|
558
552
|
Args:
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
553
|
+
retries (int, optional):
|
554
|
+
The number of retry attempts before giving up. Defaults to 3.
|
555
|
+
delay (float, optional):
|
556
|
+
The initial delay between retries, in seconds. Defaults to 2.0.
|
557
|
+
backoff_factor (float, optional):
|
558
|
+
The multiplier applied to the delay for each subsequent retry, for
|
559
|
+
exponential backoff. Default to 2.0.
|
566
560
|
|
567
561
|
Returns:
|
568
|
-
|
569
|
-
|
570
|
-
|
562
|
+
Callable:
|
563
|
+
A decorated asynchronous function with retry logic based on the specified
|
564
|
+
parameters.
|
571
565
|
|
572
566
|
Examples:
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
567
|
+
>>> @CallDecorator.retry(retries=2, delay=1, backoff_factor=2)
|
568
|
+
... async def fetch_data():
|
569
|
+
... # Implementation that might fail transiently
|
570
|
+
... raise ConnectionError("Temporary failure")
|
571
|
+
... # `fetch_data` will automatically retry on ConnectionError, up to 2 times,
|
572
|
+
... # with delays of 1s and 2s.
|
579
573
|
"""
|
580
574
|
|
581
575
|
def decorator(func: Callable[..., Any]) -> Callable:
|
@@ -607,21 +601,21 @@ class CallDecorator:
|
|
607
601
|
value can prevent the application from crashing or halting due to minor errors.
|
608
602
|
|
609
603
|
Args:
|
610
|
-
|
611
|
-
|
604
|
+
default_value (Any):
|
605
|
+
The value to return if the decorated function raises an exception.
|
612
606
|
|
613
607
|
Returns:
|
614
|
-
|
615
|
-
|
616
|
-
|
608
|
+
Callable:
|
609
|
+
A decorated asynchronous function that returns `default_value` in case of
|
610
|
+
error.
|
617
611
|
|
618
612
|
Examples:
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
613
|
+
>>> @CallDecorator.default(default_value="Fetch failed")
|
614
|
+
... async def get_resource():
|
615
|
+
... # Implementation that might raise an exception
|
616
|
+
... raise RuntimeError("Resource not available")
|
617
|
+
... # Executing `get_resource` will return "Fetch failed" instead of raising
|
618
|
+
... # an error
|
625
619
|
"""
|
626
620
|
|
627
621
|
def decorator(func: Callable[..., Any]) -> Callable:
|
@@ -646,21 +640,21 @@ class CallDecorator:
|
|
646
640
|
function does not execute more frequently than the allowed rate.
|
647
641
|
|
648
642
|
Args:
|
649
|
-
|
650
|
-
|
651
|
-
|
643
|
+
period (int):
|
644
|
+
The minimum time interval, in seconds, between consecutive calls to the
|
645
|
+
decorated function.
|
652
646
|
|
653
647
|
Returns:
|
654
|
-
|
655
|
-
|
656
|
-
|
648
|
+
Callable:
|
649
|
+
A decorated asynchronous function that adheres to the specified call
|
650
|
+
frequency limit.
|
657
651
|
|
658
652
|
Examples:
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
653
|
+
>>> @CallDecorator.throttle(2)
|
654
|
+
... async def fetch_data():
|
655
|
+
... # Implementation that fetches data from an external source
|
656
|
+
... pass
|
657
|
+
... # `fetch_data` will not be called more often than once every 2 seconds.
|
664
658
|
"""
|
665
659
|
return Throttle(period)
|
666
660
|
|
@@ -678,21 +672,21 @@ class CallDecorator:
|
|
678
672
|
succinctly applied to a collection of asynchronous results.
|
679
673
|
|
680
674
|
Args:
|
681
|
-
|
682
|
-
|
683
|
-
|
675
|
+
function (Callable[[Any], Any]):
|
676
|
+
A mapping function to apply to each element of the list returned by the
|
677
|
+
decorated function.
|
684
678
|
|
685
679
|
Returns:
|
686
|
-
|
687
|
-
|
688
|
-
|
680
|
+
Callable:
|
681
|
+
A decorated asynchronous function whose results are transformed by the
|
682
|
+
specified mapping function.
|
689
683
|
|
690
684
|
Examples:
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
685
|
+
>>> @CallDecorator.map(lambda x: x.upper())
|
686
|
+
... async def get_names():
|
687
|
+
... # Asynchronously fetches a list of names
|
688
|
+
... return ["alice", "bob", "charlie"]
|
689
|
+
... # `get_names` now returns ["ALICE", "BOB", "CHARLIE"]
|
696
690
|
"""
|
697
691
|
|
698
692
|
def decorator(func: Callable[..., list[Any]]) -> Callable:
|
@@ -729,28 +723,28 @@ class CallDecorator:
|
|
729
723
|
all asynchronous).
|
730
724
|
|
731
725
|
Args:
|
732
|
-
|
733
|
-
|
734
|
-
|
726
|
+
*functions (Callable[[Any], Any]):
|
727
|
+
A variable number of functions that are to be composed together. Each
|
728
|
+
function must accept a single argument and return a value.
|
735
729
|
|
736
730
|
Returns:
|
737
|
-
|
738
|
-
|
739
|
-
|
731
|
+
Callable:
|
732
|
+
A decorator that, when applied to a function, composes it with the
|
733
|
+
specified functions, creating a pipeline of function calls.
|
740
734
|
|
741
735
|
Raises:
|
742
|
-
|
743
|
-
|
744
|
-
|
736
|
+
ValueError:
|
737
|
+
If the provided functions mix synchronous and asynchronous types, as they
|
738
|
+
cannot be composed together.
|
745
739
|
|
746
740
|
Examples:
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
741
|
+
>>> def double(x): return x * 2
|
742
|
+
>>> def increment(x): return x + 1
|
743
|
+
>>> @CallDecorator.compose(increment, double)
|
744
|
+
... def start_value(x):
|
745
|
+
... return x
|
746
|
+
>>> start_value(3)
|
747
|
+
7 # The value is doubled to 6, then incremented to 7
|
754
748
|
"""
|
755
749
|
|
756
750
|
def decorator(func: Callable) -> Callable:
|
@@ -807,24 +801,24 @@ class CallDecorator:
|
|
807
801
|
function's output, such as formatting results or applying additional computations.
|
808
802
|
|
809
803
|
Args:
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
804
|
+
preprocess (Callable[..., Any]):
|
805
|
+
A function to preprocess the arguments passed to the decorated function.
|
806
|
+
It must accept the same arguments as the decorated function.
|
807
|
+
postprocess (Callable[..., Any]):
|
808
|
+
A function to postprocess the result of the decorated function. It must
|
809
|
+
accept a single argument, which is the output of the decorated function.
|
816
810
|
|
817
811
|
Returns:
|
818
|
-
|
819
|
-
|
820
|
-
|
812
|
+
Callable:
|
813
|
+
A decorated function that applies the specified preprocessing and
|
814
|
+
postprocessing steps to its execution.
|
821
815
|
|
822
816
|
Examples:
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
817
|
+
>>> @CallDecorator.pre_post_process(lambda x: x - 1, lambda x: x * 2)
|
818
|
+
... async def process_value(x):
|
819
|
+
... return x + 2
|
820
|
+
>>> asyncio.run(process_value(5))
|
821
|
+
12 # Input 5 is preprocessed to 4, processed to 6, and postprocessed to 12
|
828
822
|
"""
|
829
823
|
|
830
824
|
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
@@ -866,16 +860,16 @@ class CallDecorator:
|
|
866
860
|
synchronous functions.
|
867
861
|
|
868
862
|
Returns:
|
869
|
-
|
870
|
-
|
863
|
+
Callable: A decorated version of the function with caching applied. Subsequent
|
864
|
+
calls with the same arguments within the TTL will return the cached result.
|
871
865
|
|
872
866
|
Examples:
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
867
|
+
>>> @CallDecorator.cache(ttl=10)
|
868
|
+
... async def fetch_data(key):
|
869
|
+
... # Simulate a database fetch
|
870
|
+
... return "data for " + key
|
871
|
+
... # Subsequent calls to `fetch_data` with the same `key` within 10 seconds
|
872
|
+
... # will return the cached result without re-executing the function body.
|
879
873
|
"""
|
880
874
|
|
881
875
|
if AsyncUtil.is_coroutine_func(func):
|
@@ -911,21 +905,21 @@ class CallDecorator:
|
|
911
905
|
functions returning lists.
|
912
906
|
|
913
907
|
Args:
|
914
|
-
|
915
|
-
|
916
|
-
|
908
|
+
predicate (Callable[[Any], bool]):
|
909
|
+
A function that evaluates each input_ in the list. Items for which the
|
910
|
+
predicate returns True are included in the final result.
|
917
911
|
|
918
912
|
Returns:
|
919
|
-
|
920
|
-
|
913
|
+
Callable:
|
914
|
+
A decorated function that filters its list result according to the predicate.
|
921
915
|
|
922
916
|
Examples:
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
917
|
+
>>> @CallDecorator.filter(lambda x: x % 2 == 0)
|
918
|
+
... async def get_even_numbers():
|
919
|
+
... return [1, 2, 3, 4, 5]
|
920
|
+
>>> asyncio.run(get_even_numbers())
|
921
|
+
[2, 4]
|
922
|
+
... # The result list is filtered to include only even numbers.
|
929
923
|
"""
|
930
924
|
|
931
925
|
def decorator(func: Callable[..., list[Any]]) -> Callable:
|
@@ -958,27 +952,27 @@ class CallDecorator:
|
|
958
952
|
synchronous and asynchronous functions.
|
959
953
|
|
960
954
|
Args:
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
955
|
+
function (Callable[[Any, Any], Any]):
|
956
|
+
The reduction function to apply to the list. It should take two arguments
|
957
|
+
and return a single value that is the result of combining them.
|
958
|
+
initial (Any):
|
959
|
+
The initial value for the reduction process. This value is used as the
|
960
|
+
starting point for the reduction and should be an identity value for the
|
961
|
+
reduction operation.
|
968
962
|
|
969
963
|
Returns:
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
964
|
+
Callable:
|
965
|
+
A decorated function that applies the specified reduction to its list
|
966
|
+
result,
|
967
|
+
producing a single aggregated value.
|
974
968
|
|
975
969
|
Examples:
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
970
|
+
>>> @CallDecorator.reduce(lambda x, y: x + y, 0)
|
971
|
+
... async def sum_numbers():
|
972
|
+
... return [1, 2, 3, 4]
|
973
|
+
>>> asyncio.run(sum_numbers())
|
974
|
+
10
|
975
|
+
... # The numbers in the list are summed, resulting in a single value.
|
982
976
|
"""
|
983
977
|
|
984
978
|
def decorator(func: Callable[..., list[Any]]) -> Callable:
|
@@ -1011,20 +1005,20 @@ class CallDecorator:
|
|
1011
1005
|
that can only handle a limited amount of concurrent requests.
|
1012
1006
|
|
1013
1007
|
Args:
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1008
|
+
limit (int):
|
1009
|
+
The maximum number of concurrent executions allowed for the decorated
|
1010
|
+
function.
|
1017
1011
|
|
1018
1012
|
Returns:
|
1019
|
-
|
1020
|
-
|
1013
|
+
Callable:
|
1014
|
+
An asynchronous function wrapper that enforces the concurrency limit.
|
1021
1015
|
|
1022
1016
|
Examples:
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1017
|
+
>>> @CallDecorator.max_concurrency(3)
|
1018
|
+
... async def process_data(input_):
|
1019
|
+
... # Asynchronous processing logic here
|
1020
|
+
... pass
|
1021
|
+
... # No more than 3 instances of `process_data` will run concurrently.
|
1028
1022
|
"""
|
1029
1023
|
|
1030
1024
|
def decorator(func: Callable) -> Callable:
|
@@ -1092,11 +1086,11 @@ class Throttle:
|
|
1092
1086
|
this constraint.
|
1093
1087
|
|
1094
1088
|
Attributes:
|
1095
|
-
|
1089
|
+
period (int): The minimum time period (in seconds) between successive calls.
|
1096
1090
|
|
1097
1091
|
Methods:
|
1098
|
-
|
1099
|
-
|
1092
|
+
__call__: Decorates a synchronous function with throttling.
|
1093
|
+
__call_async__: Decorates an asynchronous function with throttling.
|
1100
1094
|
"""
|
1101
1095
|
|
1102
1096
|
def __init__(self, period: int) -> None:
|
@@ -1104,7 +1098,7 @@ class Throttle:
|
|
1104
1098
|
Initializes a new instance of _Throttle.
|
1105
1099
|
|
1106
1100
|
Args:
|
1107
|
-
|
1101
|
+
period (int): The minimum time period (in seconds) between successive calls.
|
1108
1102
|
"""
|
1109
1103
|
self.period = period
|
1110
1104
|
self.last_called = 0
|
@@ -1114,10 +1108,10 @@ class Throttle:
|
|
1114
1108
|
Decorates a synchronous function with the throttling mechanism.
|
1115
1109
|
|
1116
1110
|
Args:
|
1117
|
-
|
1111
|
+
func (Callable[..., Any]): The synchronous function to be throttled.
|
1118
1112
|
|
1119
1113
|
Returns:
|
1120
|
-
|
1114
|
+
Callable[..., Any]: The throttled synchronous function.
|
1121
1115
|
"""
|
1122
1116
|
|
1123
1117
|
@functools.wraps(func)
|
@@ -1135,10 +1129,10 @@ class Throttle:
|
|
1135
1129
|
Decorates an asynchronous function with the throttling mechanism.
|
1136
1130
|
|
1137
1131
|
Args:
|
1138
|
-
|
1132
|
+
func (Callable[..., Any]): The asynchronous function to be throttled.
|
1139
1133
|
|
1140
1134
|
Returns:
|
1141
|
-
|
1135
|
+
Callable[..., Any]: The throttled asynchronous function.
|
1142
1136
|
"""
|
1143
1137
|
|
1144
1138
|
@functools.wraps(func)
|
@@ -1158,18 +1152,17 @@ def _custom_error_handler(error: Exception, error_map: dict[type, Callable]) ->
|
|
1158
1152
|
handle errors based on a given error mapping.
|
1159
1153
|
|
1160
1154
|
Args:
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1155
|
+
error (Exception):
|
1156
|
+
The error to handle.
|
1157
|
+
error_map (Dict[type, Callable]):
|
1158
|
+
A dictionary mapping error types to handler functions.
|
1165
1159
|
|
1166
1160
|
examples:
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1161
|
+
>>> def handle_value_error(e): print("ValueError occurred")
|
1162
|
+
>>> custom_error_handler(ValueError(), {ValueError: handle_value_error})
|
1163
|
+
ValueError occurred
|
1170
1164
|
"""
|
1171
|
-
handler
|
1172
|
-
if handler:
|
1165
|
+
if handler := error_map.get(type(error)):
|
1173
1166
|
handler(error)
|
1174
1167
|
else:
|
1175
1168
|
logging.error(f"Unhandled error: {error}")
|
@@ -1183,44 +1176,43 @@ async def call_handler(
|
|
1183
1176
|
functions.
|
1184
1177
|
|
1185
1178
|
Args:
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1179
|
+
func (Callable):
|
1180
|
+
The function to call.
|
1181
|
+
*args:
|
1182
|
+
Positional arguments to pass to the function.
|
1183
|
+
error_map (Dict[type, Callable], optional):
|
1184
|
+
A dictionary mapping error types to handler functions.
|
1185
|
+
**kwargs:
|
1186
|
+
Keyword arguments to pass to the function.
|
1194
1187
|
|
1195
1188
|
Returns:
|
1196
|
-
|
1189
|
+
Any: The result of the function call.
|
1197
1190
|
|
1198
1191
|
Raises:
|
1199
|
-
|
1192
|
+
Exception: Propagates any exceptions not handled by the error_map.
|
1200
1193
|
|
1201
1194
|
examples:
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1195
|
+
>>> async def async_add(x, y): return x + y
|
1196
|
+
>>> asyncio.run(_call_handler(async_add, 1, 2))
|
1197
|
+
3
|
1205
1198
|
"""
|
1206
1199
|
try:
|
1207
|
-
if is_coroutine_func(func):
|
1208
|
-
# Checking for a running event loop
|
1209
|
-
try:
|
1210
|
-
loop = asyncio.get_running_loop()
|
1211
|
-
except RuntimeError: # No running event loop
|
1212
|
-
loop = asyncio.new_event_loop()
|
1213
|
-
result = loop.run_until_complete(func(*args, **kwargs))
|
1214
|
-
|
1215
|
-
loop.close()
|
1216
|
-
return result
|
1217
|
-
|
1218
|
-
if loop.is_running():
|
1219
|
-
return await func(*args, **kwargs)
|
1220
|
-
|
1221
|
-
else:
|
1200
|
+
if not is_coroutine_func(func):
|
1222
1201
|
return func(*args, **kwargs)
|
1223
1202
|
|
1203
|
+
# Checking for a running event loop
|
1204
|
+
try:
|
1205
|
+
loop = asyncio.get_running_loop()
|
1206
|
+
except RuntimeError: # No running event loop
|
1207
|
+
loop = asyncio.new_event_loop()
|
1208
|
+
result = loop.run_until_complete(func(*args, **kwargs))
|
1209
|
+
|
1210
|
+
loop.close()
|
1211
|
+
return result
|
1212
|
+
|
1213
|
+
if loop.is_running():
|
1214
|
+
return await func(*args, **kwargs)
|
1215
|
+
|
1224
1216
|
except Exception as e:
|
1225
1217
|
if error_map:
|
1226
1218
|
_custom_error_handler(e, error_map)
|
@@ -1241,20 +1233,20 @@ def is_coroutine_func(func: Callable) -> bool:
|
|
1241
1233
|
asynchronous codebases correctly.
|
1242
1234
|
|
1243
1235
|
Args:
|
1244
|
-
|
1245
|
-
|
1236
|
+
func (Callable):
|
1237
|
+
The function to check for coroutine compatibility.
|
1246
1238
|
|
1247
1239
|
Returns:
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1240
|
+
bool:
|
1241
|
+
True if `func` is an asyncio coroutine function, False otherwise. this
|
1242
|
+
determination is based on whether the function is defined with `async def`.
|
1251
1243
|
|
1252
1244
|
examples:
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1245
|
+
>>> async def async_func(): pass
|
1246
|
+
>>> def sync_func(): pass
|
1247
|
+
>>> is_coroutine_func(async_func)
|
1248
|
+
True
|
1249
|
+
>>> is_coroutine_func(sync_func)
|
1250
|
+
False
|
1259
1251
|
"""
|
1260
1252
|
return asyncio.iscoroutinefunction(func)
|