lionagi 0.0.306__py3-none-any.whl → 0.0.308__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 +2 -5
- lionagi/core/__init__.py +7 -5
- lionagi/core/agent/__init__.py +3 -0
- lionagi/core/agent/base_agent.py +10 -12
- lionagi/core/branch/__init__.py +4 -0
- lionagi/core/branch/base_branch.py +81 -81
- lionagi/core/branch/branch.py +16 -28
- lionagi/core/branch/branch_flow_mixin.py +3 -7
- lionagi/core/branch/executable_branch.py +86 -56
- lionagi/core/branch/util.py +77 -162
- lionagi/core/{flow/direct → direct}/__init__.py +1 -1
- lionagi/core/{flow/direct/predict.py → direct/parallel_predict.py} +39 -17
- 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/{flow/direct → direct}/react.py +2 -2
- lionagi/core/{flow/direct → direct}/score.py +28 -23
- lionagi/core/{flow/direct → direct}/select.py +48 -45
- lionagi/core/direct/utils.py +83 -0
- lionagi/core/flow/monoflow/ReAct.py +6 -5
- lionagi/core/flow/monoflow/__init__.py +9 -0
- lionagi/core/flow/monoflow/chat.py +10 -10
- lionagi/core/flow/monoflow/chat_mixin.py +11 -10
- lionagi/core/flow/monoflow/followup.py +6 -5
- lionagi/core/flow/polyflow/__init__.py +1 -0
- lionagi/core/flow/polyflow/chat.py +15 -3
- lionagi/core/mail/mail_manager.py +18 -19
- lionagi/core/mail/schema.py +5 -4
- lionagi/core/messages/schema.py +18 -20
- lionagi/core/prompt/__init__.py +0 -0
- lionagi/core/prompt/prompt_template.py +0 -0
- lionagi/core/schema/__init__.py +2 -2
- lionagi/core/schema/action_node.py +11 -3
- lionagi/core/schema/base_mixin.py +56 -59
- lionagi/core/schema/base_node.py +34 -37
- lionagi/core/schema/condition.py +24 -0
- lionagi/core/schema/data_logger.py +96 -99
- lionagi/core/schema/data_node.py +19 -19
- lionagi/core/schema/prompt_template.py +0 -0
- lionagi/core/schema/structure.py +171 -169
- 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 +161 -165
- lionagi/libs/sys_util.py +52 -52
- 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.306.dist-info → lionagi-0.0.308.dist-info}/METADATA +4 -2
- lionagi-0.0.308.dist-info/RECORD +115 -0
- lionagi/core/flow/direct/utils.py +0 -43
- lionagi-0.0.306.dist-info/RECORD +0 -106
- /lionagi/core/{flow/direct → direct}/sentiment.py +0 -0
- {lionagi-0.0.306.dist-info → lionagi-0.0.308.dist-info}/LICENSE +0 -0
- {lionagi-0.0.306.dist-info → lionagi-0.0.308.dist-info}/WHEEL +0 -0
- {lionagi-0.0.306.dist-info → lionagi-0.0.308.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)
|