lionagi 0.0.115__py3-none-any.whl → 0.0.204__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- lionagi/__init__.py +1 -2
- lionagi/_services/__init__.py +5 -0
- lionagi/_services/anthropic.py +79 -0
- lionagi/_services/base_service.py +414 -0
- lionagi/_services/oai.py +98 -0
- lionagi/_services/openrouter.py +44 -0
- lionagi/_services/services.py +91 -0
- lionagi/_services/transformers.py +46 -0
- lionagi/bridge/langchain.py +26 -16
- lionagi/bridge/llama_index.py +50 -20
- lionagi/configs/oai_configs.py +2 -14
- lionagi/configs/openrouter_configs.py +2 -2
- lionagi/core/__init__.py +7 -8
- lionagi/core/branch/branch.py +589 -0
- lionagi/core/branch/branch_manager.py +139 -0
- lionagi/core/branch/conversation.py +484 -0
- lionagi/core/core_util.py +59 -0
- lionagi/core/flow/flow.py +19 -0
- lionagi/core/flow/flow_util.py +62 -0
- lionagi/core/instruction_set/__init__.py +0 -5
- lionagi/core/instruction_set/instruction_set.py +343 -0
- lionagi/core/messages/messages.py +176 -0
- lionagi/core/sessions/__init__.py +0 -5
- lionagi/core/sessions/session.py +428 -0
- lionagi/loaders/chunker.py +51 -47
- lionagi/loaders/load_util.py +2 -2
- lionagi/loaders/reader.py +45 -39
- lionagi/models/imodel.py +53 -0
- lionagi/schema/async_queue.py +158 -0
- lionagi/schema/base_node.py +318 -147
- lionagi/schema/base_tool.py +31 -1
- lionagi/schema/data_logger.py +74 -38
- lionagi/schema/data_node.py +57 -6
- lionagi/structures/graph.py +132 -10
- lionagi/structures/relationship.py +58 -20
- lionagi/structures/structure.py +36 -25
- lionagi/tests/test_utils/test_api_util.py +219 -0
- lionagi/tests/test_utils/test_call_util.py +785 -0
- lionagi/tests/test_utils/test_encrypt_util.py +323 -0
- lionagi/tests/test_utils/test_io_util.py +238 -0
- lionagi/tests/test_utils/test_nested_util.py +338 -0
- lionagi/tests/test_utils/test_sys_util.py +358 -0
- lionagi/tools/tool_manager.py +186 -0
- lionagi/tools/tool_util.py +266 -3
- lionagi/utils/__init__.py +21 -61
- lionagi/utils/api_util.py +359 -71
- lionagi/utils/call_util.py +839 -264
- lionagi/utils/encrypt_util.py +283 -16
- lionagi/utils/io_util.py +178 -93
- lionagi/utils/nested_util.py +672 -0
- lionagi/utils/pd_util.py +57 -0
- lionagi/utils/sys_util.py +284 -156
- lionagi/utils/url_util.py +55 -0
- lionagi/version.py +1 -1
- {lionagi-0.0.115.dist-info → lionagi-0.0.204.dist-info}/METADATA +21 -17
- lionagi-0.0.204.dist-info/RECORD +106 -0
- lionagi/core/conversations/__init__.py +0 -5
- lionagi/core/conversations/conversation.py +0 -107
- lionagi/core/flows/__init__.py +0 -8
- lionagi/core/flows/flow.py +0 -8
- lionagi/core/flows/flow_util.py +0 -62
- lionagi/core/instruction_set/instruction_sets.py +0 -7
- lionagi/core/sessions/sessions.py +0 -185
- lionagi/endpoints/__init__.py +0 -5
- lionagi/endpoints/audio.py +0 -17
- lionagi/endpoints/chatcompletion.py +0 -54
- lionagi/messages/__init__.py +0 -11
- lionagi/messages/instruction.py +0 -15
- lionagi/messages/message.py +0 -110
- lionagi/messages/response.py +0 -33
- lionagi/messages/system.py +0 -12
- lionagi/objs/__init__.py +0 -11
- lionagi/objs/abc_objs.py +0 -39
- lionagi/objs/async_queue.py +0 -135
- lionagi/objs/messenger.py +0 -85
- lionagi/objs/tool_manager.py +0 -253
- lionagi/services/__init__.py +0 -11
- lionagi/services/base_api_service.py +0 -230
- lionagi/services/oai.py +0 -34
- lionagi/services/openrouter.py +0 -31
- lionagi/tests/test_api_util.py +0 -46
- lionagi/tests/test_call_util.py +0 -115
- lionagi/tests/test_convert_util.py +0 -202
- lionagi/tests/test_encrypt_util.py +0 -33
- lionagi/tests/test_flat_util.py +0 -426
- lionagi/tests/test_sys_util.py +0 -0
- lionagi/utils/convert_util.py +0 -229
- lionagi/utils/flat_util.py +0 -599
- lionagi-0.0.115.dist-info/RECORD +0 -110
- /lionagi/{services → _services}/anyscale.py +0 -0
- /lionagi/{services → _services}/azure.py +0 -0
- /lionagi/{services → _services}/bedrock.py +0 -0
- /lionagi/{services → _services}/everlyai.py +0 -0
- /lionagi/{services → _services}/gemini.py +0 -0
- /lionagi/{services → _services}/gpt4all.py +0 -0
- /lionagi/{services → _services}/huggingface.py +0 -0
- /lionagi/{services → _services}/litellm.py +0 -0
- /lionagi/{services → _services}/localai.py +0 -0
- /lionagi/{services → _services}/mistralai.py +0 -0
- /lionagi/{services → _services}/ollama.py +0 -0
- /lionagi/{services → _services}/openllm.py +0 -0
- /lionagi/{services → _services}/perplexity.py +0 -0
- /lionagi/{services → _services}/predibase.py +0 -0
- /lionagi/{services → _services}/rungpt.py +0 -0
- /lionagi/{services → _services}/vllm.py +0 -0
- /lionagi/{services → _services}/xinference.py +0 -0
- /lionagi/{endpoints/assistants.py → agents/__init__.py} +0 -0
- /lionagi/{tools → agents}/planner.py +0 -0
- /lionagi/{tools → agents}/prompter.py +0 -0
- /lionagi/{tools → agents}/scorer.py +0 -0
- /lionagi/{tools → agents}/summarizer.py +0 -0
- /lionagi/{tools → agents}/validator.py +0 -0
- /lionagi/{endpoints/embeddings.py → core/branch/__init__.py} +0 -0
- /lionagi/{services/anthropic.py → core/branch/cluster.py} +0 -0
- /lionagi/{endpoints/finetune.py → core/flow/__init__.py} +0 -0
- /lionagi/{endpoints/image.py → core/messages/__init__.py} +0 -0
- /lionagi/{endpoints/moderation.py → models/__init__.py} +0 -0
- /lionagi/{endpoints/vision.py → models/base_model.py} +0 -0
- /lionagi/{objs → schema}/status_tracker.py +0 -0
- /lionagi/tests/{test_io_util.py → test_utils/__init__.py} +0 -0
- {lionagi-0.0.115.dist-info → lionagi-0.0.204.dist-info}/LICENSE +0 -0
- {lionagi-0.0.115.dist-info → lionagi-0.0.204.dist-info}/WHEEL +0 -0
- {lionagi-0.0.115.dist-info → lionagi-0.0.204.dist-info}/top_level.txt +0 -0
lionagi/utils/call_util.py
CHANGED
@@ -1,348 +1,923 @@
|
|
1
|
-
# use sys_util and flat_util
|
2
1
|
import asyncio
|
2
|
+
import functools
|
3
|
+
import logging
|
3
4
|
import time
|
4
|
-
from typing import Any, Callable, List, Optional,
|
5
|
+
from typing import Any, Callable, Generator, Iterable, List, Dict, Optional, Tuple
|
5
6
|
|
6
|
-
from
|
7
|
-
from .flat_util import to_list
|
7
|
+
from aiocache import cached
|
8
8
|
|
9
9
|
|
10
|
-
def
|
11
|
-
input: Any, func: Callable, sleep: int = 0.1,
|
12
|
-
message: Optional[str] = None,
|
13
|
-
ignore_error: bool = False, **kwargs
|
14
|
-
) -> Any:
|
10
|
+
def to_list(input: Any, flatten: bool = True, dropna: bool = False) -> List[Any]:
|
15
11
|
"""
|
16
|
-
|
12
|
+
Convert input to a list, with options to flatten and drop None values.
|
17
13
|
|
18
|
-
|
19
|
-
|
14
|
+
Args:
|
15
|
+
input (Any): The input to convert.
|
16
|
+
flatten (bool): If True, flattens the list if input is a list. Default is True.
|
17
|
+
dropna (bool): If True, None values are removed from the list. Default is False.
|
20
18
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
func (Callable): Function to execute.
|
25
|
-
|
26
|
-
sleep (int, optional): Time in seconds to wait before calling the function. Defaults to 0.1.
|
19
|
+
Returns:
|
20
|
+
List[Any]: The input converted to a list.
|
27
21
|
|
28
|
-
|
22
|
+
Examples:
|
23
|
+
>>> to_list([1, [2, None], 3], flatten=True, dropna=True)
|
24
|
+
[1, 2, 3]
|
25
|
+
>>> to_list("hello", flatten=False)
|
26
|
+
["hello"]
|
27
|
+
"""
|
28
|
+
if isinstance(input, list) and flatten:
|
29
|
+
input = _flatten_list(input)
|
30
|
+
elif isinstance(input, Iterable) and not isinstance(input, (str, dict)):
|
31
|
+
try:
|
32
|
+
input = list(input)
|
33
|
+
except Exception as e:
|
34
|
+
raise ValueError("Input cannot be converted to a list.") from e
|
35
|
+
else:
|
36
|
+
input = [input]
|
37
|
+
if dropna:
|
38
|
+
input = _dropna(input)
|
39
|
+
return input
|
29
40
|
|
30
|
-
|
41
|
+
def lcall(
|
42
|
+
input: Any, func: Callable, flatten: bool = False,
|
43
|
+
dropna: bool = False, **kwargs
|
44
|
+
) -> List[Any]:
|
45
|
+
"""
|
46
|
+
Apply a function to each element of the input list, with options to flatten and drop None values.
|
31
47
|
|
32
|
-
|
48
|
+
Args:
|
49
|
+
input (Any): The input to process.
|
50
|
+
func (Callable): The function to apply to each element.
|
51
|
+
flatten (bool): If True, flattens the result. Default is False.
|
52
|
+
dropna (bool): If True, None values are removed from the input. Default is False.
|
53
|
+
**kwargs: Additional keyword arguments to pass to func.
|
33
54
|
|
34
55
|
Returns:
|
35
|
-
Any:
|
56
|
+
List[Any]: A list of results after applying the function.
|
36
57
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
... return x + 1
|
43
|
-
>>> hold_call(5, add_one, sleep=2)
|
44
|
-
6
|
58
|
+
Examples:
|
59
|
+
>>> lcall([1, 2, 3], lambda x: x * 2)
|
60
|
+
[2, 4, 6]
|
61
|
+
>>> lcall([1, 2, None], lambda x: x and x * 2, dropna=True)
|
62
|
+
[2, 4]
|
45
63
|
"""
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
raise
|
56
|
-
|
57
|
-
async def ahcall(
|
58
|
-
input_: Any, func: Callable, sleep: int = 5,
|
59
|
-
message: Optional[str] = None,
|
60
|
-
ignore_error: bool = False, **kwargs
|
61
|
-
) -> Any:
|
64
|
+
lst = to_list(input=input, dropna=dropna)
|
65
|
+
if len(to_list(func)) != 1:
|
66
|
+
raise ValueError("There must be one and only one function for list calling.")
|
67
|
+
if flatten:
|
68
|
+
return to_list([func(i, **kwargs) for i in lst])
|
69
|
+
return [func(i, **kwargs) for i in lst]
|
70
|
+
|
71
|
+
@functools.lru_cache(maxsize=None)
|
72
|
+
def is_coroutine_func(func: Callable) -> bool:
|
62
73
|
"""
|
63
|
-
|
74
|
+
Check if the given function is a coroutine function.
|
64
75
|
|
65
|
-
|
66
|
-
|
76
|
+
Args:
|
77
|
+
func (Callable): The function to check.
|
67
78
|
|
68
|
-
|
69
|
-
|
79
|
+
Returns:
|
80
|
+
bool: True if the function is a coroutine function, False otherwise.
|
81
|
+
|
82
|
+
Examples:
|
83
|
+
>>> async def async_func(): pass
|
84
|
+
>>> def sync_func(): pass
|
85
|
+
>>> is_coroutine_func(async_func)
|
86
|
+
True
|
87
|
+
>>> is_coroutine_func(sync_func)
|
88
|
+
False
|
89
|
+
"""
|
90
|
+
return asyncio.iscoroutinefunction(func)
|
70
91
|
|
71
|
-
|
92
|
+
async def alcall(
|
93
|
+
input: Any, func: Callable, flatten: bool = False, **kwargs
|
94
|
+
)-> List[Any]:
|
95
|
+
"""
|
96
|
+
Asynchronously apply a function to each element in the input.
|
72
97
|
|
73
|
-
|
98
|
+
Args:
|
99
|
+
input (Any): The input to process.
|
100
|
+
func (Callable): The function to apply.
|
101
|
+
flatten (bool, optional): Whether to flatten the result. Default is False.
|
102
|
+
**kwargs: Keyword arguments to pass to the function.
|
74
103
|
|
75
|
-
|
104
|
+
Returns:
|
105
|
+
List[Any]: A list of results after asynchronously applying the function.
|
76
106
|
|
77
|
-
|
107
|
+
Examples:
|
108
|
+
>>> async def square(x): return x * x
|
109
|
+
>>> asyncio.run(alcall([1, 2, 3], square))
|
110
|
+
[1, 4, 9]
|
111
|
+
"""
|
112
|
+
lst = to_list(input=input)
|
113
|
+
tasks = [func(i, **kwargs) for i in lst]
|
114
|
+
outs = await asyncio.gather(*tasks)
|
115
|
+
return to_list(outs, flatten=flatten)
|
116
|
+
|
117
|
+
async def mcall(
|
118
|
+
input: Any, func: Any, explode: bool = False, **kwargs
|
119
|
+
) -> List[Any]:
|
120
|
+
"""
|
121
|
+
Asynchronously map a function or functions over an input or inputs.
|
78
122
|
|
79
|
-
|
123
|
+
Args:
|
124
|
+
input (Any): The input or inputs to process.
|
125
|
+
func (Any): The function or functions to apply.
|
126
|
+
explode (bool, optional): Whether to apply each function to each input. Default is False.
|
127
|
+
**kwargs: Keyword arguments to pass to the function.
|
80
128
|
|
81
129
|
Returns:
|
82
|
-
Any:
|
83
|
-
|
84
|
-
Raises:
|
85
|
-
Exception: Re-raises the exception unless 'ignore_error' is True.
|
130
|
+
List[Any]: A list of results after applying the function(s).
|
86
131
|
|
87
|
-
|
88
|
-
>>> async def
|
89
|
-
|
90
|
-
|
91
|
-
|
132
|
+
Examples:
|
133
|
+
>>> async def add_one(x): return x + 1
|
134
|
+
>>> asyncio.run(mcall([1, 2, 3], add_one))
|
135
|
+
[2, 3, 4]
|
136
|
+
|
92
137
|
"""
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
if
|
104
|
-
raise
|
138
|
+
input_ = to_list(input, dropna=True)
|
139
|
+
funcs_ = to_list(func, dropna=True)
|
140
|
+
|
141
|
+
if explode:
|
142
|
+
tasks = [
|
143
|
+
_alcall(input_, f, flatten=True, **kwargs)
|
144
|
+
for f in funcs_
|
145
|
+
]
|
146
|
+
return await asyncio.gather(*tasks)
|
147
|
+
else:
|
148
|
+
if len(input_) != len(funcs_):
|
149
|
+
raise ValueError("Inputs and functions must be the same length for map calling.")
|
150
|
+
tasks = [
|
151
|
+
_call_handler(func, inp, **kwargs)
|
152
|
+
for inp, func in zip(input, func)
|
153
|
+
]
|
154
|
+
return await asyncio.gather(*tasks)
|
105
155
|
|
106
|
-
def
|
107
|
-
input_: Any, func_: Callable, flatten: bool = False,
|
108
|
-
dropna: bool = False, **kwargs
|
109
|
-
) -> List[Any]:
|
156
|
+
async def bcall(input: Any, func: Callable, batch_size: int, **kwargs) -> List[Any]:
|
110
157
|
"""
|
111
|
-
|
112
|
-
|
113
|
-
This function converts the `input` to a list, with options to flatten structures
|
114
|
-
and lists, and then applies a given `func` to each element of the list.
|
115
|
-
|
116
|
-
Parameters:
|
117
|
-
input (Any): The input to be converted to a list and processed.
|
158
|
+
Asynchronously call a function on batches of inputs.
|
118
159
|
|
119
|
-
|
160
|
+
Args:
|
161
|
+
input (Any): The input to process.
|
162
|
+
func (Callable): The function to apply.
|
163
|
+
batch_size (int): The size of each batch.
|
164
|
+
**kwargs: Keyword arguments to pass to the function.
|
120
165
|
|
121
|
-
|
122
|
-
|
123
|
-
flat (bool, optional): If True, flattens nested lists in the input. Defaults to False.
|
166
|
+
Returns:
|
167
|
+
List[Any]: A list of results after applying the function in batches.
|
124
168
|
|
125
|
-
|
169
|
+
Examples:
|
170
|
+
>>> async def sum_batch(batch): return sum(batch)
|
171
|
+
>>> asyncio.run(bcall([1, 2, 3, 4], sum_batch, batch_size=2))
|
172
|
+
[3, 7]
|
173
|
+
"""
|
174
|
+
results = []
|
175
|
+
input = to_list(input)
|
176
|
+
for i in range(0, len(input), batch_size):
|
177
|
+
batch = input[i:i + batch_size]
|
178
|
+
batch_results = await alcall(batch, func, **kwargs)
|
179
|
+
results.extend(batch_results)
|
180
|
+
|
181
|
+
return results
|
182
|
+
|
183
|
+
async def tcall(
|
184
|
+
func: Callable, *args, delay: float = 0, err_msg: Optional[str] = None,
|
185
|
+
ignore_err: bool = False, timing: bool = False,
|
186
|
+
timeout: Optional[float] = None, **kwargs
|
187
|
+
) -> Any:
|
188
|
+
"""
|
189
|
+
Asynchronously call a function with optional delay, timeout, and error handling.
|
190
|
+
|
191
|
+
Args:
|
192
|
+
func (Callable): The function to call.
|
193
|
+
*args: Positional arguments to pass to the function.
|
194
|
+
delay (float): Delay before calling the function, in seconds.
|
195
|
+
err_msg (Optional[str]): Custom error message.
|
196
|
+
ignore_err (bool): If True, ignore errors and return default.
|
197
|
+
timing (bool): If True, return a tuple (result, duration).
|
198
|
+
default (Any): Default value to return on error.
|
199
|
+
timeout (Optional[float]): Timeout for the function call, in seconds.
|
200
|
+
**kwargs: Keyword arguments to pass to the function.
|
126
201
|
|
127
202
|
Returns:
|
128
|
-
|
129
|
-
|
130
|
-
Raises:
|
131
|
-
ValueError: If the `func` cannot be applied to the `input`.
|
203
|
+
Any: The result of the function call, or (result, duration) if timing is True.
|
132
204
|
|
133
|
-
|
134
|
-
>>> def
|
135
|
-
|
136
|
-
|
137
|
-
[1, 4, 9]
|
205
|
+
Examples:
|
206
|
+
>>> async def example_func(x): return x
|
207
|
+
>>> asyncio.run(tcall(example_func, 5, timing=True))
|
208
|
+
(5, duration)
|
138
209
|
"""
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
210
|
+
async def async_call() -> Tuple[Any, float]:
|
211
|
+
start_time = time.time()
|
212
|
+
if timeout is not None:
|
213
|
+
result = await asyncio.wait_for(func(*args, **kwargs), timeout)
|
214
|
+
try:
|
215
|
+
await asyncio.sleep(delay)
|
216
|
+
result = await func(*args, **kwargs)
|
217
|
+
duration = time.time() - start_time
|
218
|
+
return (result, duration) if timing else result
|
219
|
+
except Exception as e:
|
220
|
+
handle_error(e)
|
221
|
+
|
222
|
+
def sync_call() -> Tuple[Any, float]:
|
223
|
+
start_time = time.time()
|
224
|
+
try:
|
225
|
+
time.sleep(delay)
|
226
|
+
result = func(*args, **kwargs)
|
227
|
+
duration = time.time() - start_time
|
228
|
+
return (result, duration) if timing else result
|
229
|
+
except Exception as e:
|
230
|
+
handle_error(e)
|
231
|
+
|
232
|
+
def handle_error(e: Exception):
|
233
|
+
_msg = f"{err_msg} Error: {e}" if err_msg else f"An error occurred: {e}"
|
234
|
+
print(_msg)
|
235
|
+
if not ignore_err:
|
236
|
+
raise
|
144
237
|
|
145
|
-
|
146
|
-
|
147
|
-
|
238
|
+
if asyncio.iscoroutinefunction(func):
|
239
|
+
return await async_call()
|
240
|
+
else:
|
241
|
+
return sync_call()
|
242
|
+
|
243
|
+
async def rcall(
|
244
|
+
func: Callable, *args, retries: int = 0, delay: float = 1.0,
|
245
|
+
backoff_factor: float = 2.0, default: Any = None,
|
246
|
+
timeout: Optional[float] = None, **kwargs
|
247
|
+
) -> Any:
|
148
248
|
"""
|
149
|
-
Asynchronously
|
249
|
+
Asynchronously retry a function call with exponential backoff.
|
250
|
+
|
251
|
+
Args:
|
252
|
+
func (Callable): The function to call.
|
253
|
+
*args: Positional arguments to pass to the function.
|
254
|
+
retries (int): Number of retry attempts.
|
255
|
+
delay (float): Initial delay between retries, in seconds.
|
256
|
+
backoff_factor (float): Factor by which to multiply delay for each retry.
|
257
|
+
default (Any): Default value to return if all retries fail.
|
258
|
+
timeout (Optional[float]): Timeout for each function call, in seconds.
|
259
|
+
**kwargs: Keyword arguments to pass to the function.
|
150
260
|
|
151
|
-
|
152
|
-
|
153
|
-
each element of the list asynchronously.
|
261
|
+
Returns:
|
262
|
+
Any: The result of the function call, or default if retries are exhausted.
|
154
263
|
|
155
|
-
|
156
|
-
|
264
|
+
Examples:
|
265
|
+
>>> async def example_func(x): return x
|
266
|
+
>>> asyncio.run(rcall(example_func, 5, retries=2))
|
267
|
+
5
|
268
|
+
"""
|
269
|
+
last_exception = None
|
270
|
+
result = None
|
271
|
+
|
272
|
+
for attempt in range(retries + 1) if retries == 0 else range(retries):
|
273
|
+
try:
|
274
|
+
# Using tcall for each retry attempt with timeout and delay
|
275
|
+
result = await _tcall(func, *args, timeout=timeout, **kwargs)
|
276
|
+
return result
|
277
|
+
except Exception as e:
|
278
|
+
last_exception = e
|
279
|
+
if attempt < retries:
|
280
|
+
await asyncio.sleep(delay)
|
281
|
+
delay *= backoff_factor
|
282
|
+
else:
|
283
|
+
break
|
284
|
+
if result is None and default is not None:
|
285
|
+
return default
|
286
|
+
elif last_exception is not None:
|
287
|
+
raise last_exception
|
288
|
+
else:
|
289
|
+
raise RuntimeError("rcall failed without catching an exception")
|
290
|
+
|
291
|
+
class CallDecorator:
|
157
292
|
|
158
|
-
|
293
|
+
"""
|
294
|
+
Call Decorators
|
295
|
+
"""
|
159
296
|
|
160
|
-
flatten_dict (bool, optional): If True, flattens dictionaries in the input. Defaults to False.
|
161
297
|
|
162
|
-
|
298
|
+
@staticmethod
|
299
|
+
def timeout(timeout: int) -> Callable:
|
300
|
+
"""
|
301
|
+
Decorator to apply a timeout to an asynchronous function.
|
302
|
+
|
303
|
+
Args:
|
304
|
+
timeout (int): Timeout duration in seconds.
|
305
|
+
|
306
|
+
Returns:
|
307
|
+
Callable: A decorated function with applied timeout.
|
308
|
+
|
309
|
+
Examples:
|
310
|
+
>>> @CallDecorator.timeout(5)
|
311
|
+
... async def long_running_task(): pass
|
312
|
+
... # The task will timeout after 5 seconds
|
313
|
+
"""
|
314
|
+
def decorator(func: Callable[..., Any]) -> Callable:
|
315
|
+
@functools.wraps(func)
|
316
|
+
async def wrapper(*args, **kwargs) -> Any:
|
317
|
+
return await rcall(func, *args, timeout=timeout, **kwargs)
|
318
|
+
return wrapper
|
319
|
+
return decorator
|
320
|
+
|
321
|
+
@staticmethod
|
322
|
+
def retry(
|
323
|
+
retries: int = 3, delay: float = 2.0, backoff_factor: float = 2.0
|
324
|
+
) -> Callable:
|
325
|
+
"""
|
326
|
+
Decorator to retry an asynchronous function with exponential backoff.
|
327
|
+
|
328
|
+
Args:
|
329
|
+
retries (int): Number of retry attempts.
|
330
|
+
initial_delay (float): Initial delay between retries in seconds.
|
331
|
+
backoff_factor (float): Factor by which to multiply delay for each retry.
|
332
|
+
|
333
|
+
Returns:
|
334
|
+
Callable: A decorated function with applied retry logic.
|
335
|
+
|
336
|
+
Examples:
|
337
|
+
>>> @CallDecorator.retry(retries=2, initial_delay=1.0, backoff_factor=2.0)
|
338
|
+
... async def fetch_data(): pass
|
339
|
+
... # This function will retry up to 2 times with increasing delays
|
340
|
+
"""
|
341
|
+
def decorator(func: Callable[..., Any]) -> Callable:
|
342
|
+
@functools.wraps(func)
|
343
|
+
async def wrapper(*args, **kwargs) -> Any:
|
344
|
+
return await rcall(
|
345
|
+
func, *args, retries=retries, delay=delay,
|
346
|
+
backoff_factor=backoff_factor, **kwargs
|
347
|
+
)
|
348
|
+
return wrapper
|
349
|
+
return decorator
|
350
|
+
|
351
|
+
@staticmethod
|
352
|
+
def default(default_value: Any) -> Callable:
|
353
|
+
def decorator(func: Callable[..., Any]) -> Callable:
|
354
|
+
@functools.wraps(func)
|
355
|
+
async def wrapper(*args, **kwargs) -> Any:
|
356
|
+
return await rcall(func, *args, default=default_value, **kwargs)
|
357
|
+
return wrapper
|
358
|
+
return decorator
|
359
|
+
|
360
|
+
@staticmethod
|
361
|
+
def throttle(period: int) -> Callable:
|
362
|
+
"""
|
363
|
+
A static method to create a throttling decorator using the Throttle class.
|
364
|
+
|
365
|
+
Args:
|
366
|
+
period (int): The minimum time period (in seconds) between successive calls.
|
367
|
+
|
368
|
+
Returns:
|
369
|
+
Callable: A decorator that applies a throttling mechanism to the decorated
|
370
|
+
function.
|
371
|
+
"""
|
372
|
+
return Throttle(period)
|
373
|
+
|
374
|
+
@staticmethod
|
375
|
+
def map(function: Callable[[Any], Any]) -> Callable:
|
376
|
+
"""
|
377
|
+
Decorator that applies a mapping function to the results of an asynchronous
|
378
|
+
function.
|
379
|
+
|
380
|
+
This decorator transforms each element in the list returned by the decorated
|
381
|
+
function using the provided mapping function.
|
382
|
+
|
383
|
+
Args:
|
384
|
+
function (Callable[[Any], Any]): A function to apply to each element of the list.
|
385
|
+
|
386
|
+
Returns:
|
387
|
+
Callable: A decorated function that maps the provided function over its results.
|
388
|
+
|
389
|
+
Examples:
|
390
|
+
>>> @CallDecorator.map(lambda x: x * 2)
|
391
|
+
... async def get_numbers(): return [1, 2, 3]
|
392
|
+
>>> asyncio.run(get_numbers())
|
393
|
+
[2, 4, 6]
|
394
|
+
"""
|
395
|
+
def decorator(func: Callable[..., List[Any]]) -> Callable:
|
396
|
+
if is_coroutine_func(func):
|
397
|
+
@functools.wraps(func)
|
398
|
+
async def async_wrapper(*args, **kwargs) -> List[Any]:
|
399
|
+
values = await func(*args, **kwargs)
|
400
|
+
return [function(value) for value in values]
|
401
|
+
return async_wrapper
|
402
|
+
else:
|
403
|
+
@functools.wraps(func)
|
404
|
+
def sync_wrapper(*args, **kwargs) -> List[Any]:
|
405
|
+
values = func(*args, **kwargs)
|
406
|
+
return [function(value) for value in values]
|
407
|
+
return sync_wrapper
|
408
|
+
return decorator
|
409
|
+
|
410
|
+
@staticmethod
|
411
|
+
def compose(*functions: Callable[[Any], Any]) -> Callable:
|
412
|
+
"""
|
413
|
+
Decorator factory that composes multiple functions. The output of each
|
414
|
+
function is passed as the input to the next, in the order they are provided.
|
415
|
+
|
416
|
+
Args:
|
417
|
+
*functions: Variable length list of functions to compose.
|
418
|
+
|
419
|
+
Returns:
|
420
|
+
Callable: A new function that is the composition of the given functions.
|
421
|
+
"""
|
422
|
+
def decorator(func: Callable) -> Callable:
|
423
|
+
if not any(is_coroutine_func(f) for f in functions):
|
424
|
+
@functools.wraps(func)
|
425
|
+
def sync_wrapper(*args, **kwargs):
|
426
|
+
value = func(*args, **kwargs)
|
427
|
+
for function in functions:
|
428
|
+
try:
|
429
|
+
value = function(value)
|
430
|
+
except Exception as e:
|
431
|
+
raise ValueError(f"Error in function {function.__name__}: {e}")
|
432
|
+
return value
|
433
|
+
return sync_wrapper
|
434
|
+
elif all(is_coroutine_func(f) for f in functions):
|
435
|
+
@functools.wraps(func)
|
436
|
+
async def async_wrapper(*args, **kwargs):
|
437
|
+
value = func(*args, **kwargs)
|
438
|
+
for function in functions:
|
439
|
+
try:
|
440
|
+
value = await function(value)
|
441
|
+
except Exception as e:
|
442
|
+
raise ValueError(f"Error in function {function.__name__}: {e}")
|
443
|
+
return value
|
444
|
+
return async_wrapper
|
445
|
+
else:
|
446
|
+
raise ValueError("Cannot compose both synchronous and asynchronous functions.")
|
447
|
+
return decorator
|
448
|
+
|
449
|
+
@staticmethod
|
450
|
+
def pre_post_process(
|
451
|
+
preprocess: Callable[..., Any], postprocess: Callable[..., Any]
|
452
|
+
) -> Callable:
|
453
|
+
"""
|
454
|
+
Decorator that applies preprocessing and postprocessing functions to the arguments
|
455
|
+
and result of an asynchronous function.
|
456
|
+
|
457
|
+
Args:
|
458
|
+
preprocess (Callable[..., Any]): A function to preprocess each argument.
|
459
|
+
postprocess (Callable[..., Any]): A function to postprocess the result.
|
460
|
+
|
461
|
+
Returns:
|
462
|
+
Callable: A decorated function with preprocessing and postprocessing applied.
|
463
|
+
|
464
|
+
Examples:
|
465
|
+
>>> @CallDecorator.pre_post_process(lambda x: x * 2, lambda x: x + 1)
|
466
|
+
... async def compute(x): return x
|
467
|
+
>>> asyncio.run(compute(5))
|
468
|
+
21 # (5 * 2) -> 10, compute(10) -> 10, 10 + 1 -> 21
|
469
|
+
"""
|
470
|
+
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
471
|
+
if is_coroutine_func(func):
|
472
|
+
@functools.wraps(func)
|
473
|
+
async def async_wrapper(*args, **kwargs) -> Any:
|
474
|
+
preprocessed_args = [preprocess(arg) for arg in args]
|
475
|
+
preprocessed_kwargs = {k: preprocess(v) for k, v in kwargs.items()}
|
476
|
+
result = await func(*preprocessed_args, **preprocessed_kwargs)
|
477
|
+
return postprocess(result)
|
478
|
+
return async_wrapper
|
479
|
+
else:
|
480
|
+
@functools.wraps(func)
|
481
|
+
def sync_wrapper(*args, **kwargs) -> Any:
|
482
|
+
preprocessed_args = [preprocess(arg) for arg in args]
|
483
|
+
preprocessed_kwargs = {k: preprocess(v) for k, v in kwargs.items()}
|
484
|
+
result = func(*preprocessed_args, **preprocessed_kwargs)
|
485
|
+
return postprocess(result)
|
486
|
+
return sync_wrapper
|
487
|
+
|
488
|
+
return decorator
|
489
|
+
|
490
|
+
@staticmethod
|
491
|
+
def cache(func: Callable, ttl=600, maxsize=None) -> Callable:
|
492
|
+
"""
|
493
|
+
Decorator that caches the results of function calls (both sync and async).
|
494
|
+
If the function is called again with the same arguments,
|
495
|
+
the cached result is returned instead of re-executing the function.
|
496
|
+
|
497
|
+
Args:
|
498
|
+
func (Callable): The function (can be sync or async) whose results need to be cached.
|
499
|
+
|
500
|
+
Returns:
|
501
|
+
Callable: A decorated function with caching applied.
|
502
|
+
"""
|
503
|
+
|
504
|
+
if is_coroutine_func(func):
|
505
|
+
# Asynchronous function handling
|
506
|
+
@cached(ttl=ttl)
|
507
|
+
async def cached_async(*args, **kwargs) -> Any:
|
508
|
+
return await func(*args, **kwargs)
|
509
|
+
|
510
|
+
@functools.wraps(func)
|
511
|
+
async def async_wrapper(*args, **kwargs) -> Any:
|
512
|
+
return await cached_async(*args, **kwargs)
|
513
|
+
|
514
|
+
return async_wrapper
|
163
515
|
|
164
|
-
|
516
|
+
else:
|
517
|
+
# Synchronous function handling
|
518
|
+
@functools.lru_cache(maxsize=maxsize)
|
519
|
+
def cached_sync(*args, **kwargs) -> Any:
|
520
|
+
return func(*args, **kwargs)
|
521
|
+
|
522
|
+
@functools.wraps(func)
|
523
|
+
def sync_wrapper(*args, **kwargs) -> Any:
|
524
|
+
return cached_sync(*args, **kwargs)
|
525
|
+
|
526
|
+
return sync_wrapper
|
527
|
+
|
528
|
+
@staticmethod
|
529
|
+
def filter(predicate: Callable[[Any], bool]) -> Callable:
|
530
|
+
"""
|
531
|
+
Decorator that filters the results of an asynchronous function based on a predicate
|
532
|
+
function.
|
533
|
+
|
534
|
+
Args:
|
535
|
+
predicate (Callable[[Any], bool]): A function that returns True for items to keep.
|
536
|
+
|
537
|
+
Returns:
|
538
|
+
Callable: A decorated function that filters its results based on the predicate.
|
539
|
+
|
540
|
+
Examples:
|
541
|
+
>>> @CallDecorator.filter(lambda x: x % 2 == 0)
|
542
|
+
... async def get_numbers(): return [1, 2, 3, 4, 5]
|
543
|
+
>>> asyncio.run(get_numbers())
|
544
|
+
[2, 4]
|
545
|
+
"""
|
546
|
+
def decorator(func: Callable[..., List[Any]]) -> Callable:
|
547
|
+
if is_coroutine_func(func):
|
548
|
+
@functools.wraps(func)
|
549
|
+
async def wrapper(*args, **kwargs) -> List[Any]:
|
550
|
+
values = await func(*args, **kwargs)
|
551
|
+
return [value for value in values if predicate(value)]
|
552
|
+
return wrapper
|
553
|
+
else:
|
554
|
+
@functools.wraps(func)
|
555
|
+
def wrapper(*args, **kwargs) -> List[Any]:
|
556
|
+
values = func(*args, **kwargs)
|
557
|
+
return [value for value in values if predicate(value)]
|
558
|
+
return wrapper
|
559
|
+
return decorator
|
560
|
+
|
561
|
+
@staticmethod
|
562
|
+
def reduce(function: Callable[[Any, Any], Any], initial: Any) -> Callable:
|
563
|
+
"""
|
564
|
+
Decorator that reduces the results of an asynchronous function to a single value using
|
565
|
+
the specified reduction function.
|
566
|
+
|
567
|
+
Args:
|
568
|
+
function (Callable[[Any, Any], Any]): A reduction function to apply.
|
569
|
+
initial (Any): The initial value for the reduction.
|
570
|
+
|
571
|
+
Returns:
|
572
|
+
Callable: A decorated function that reduces its results to a single value.
|
573
|
+
|
574
|
+
Examples:
|
575
|
+
>>> @CallDecorator.reduce(lambda x, y: x + y, 0)
|
576
|
+
... async def get_numbers(): return [1, 2, 3, 4]
|
577
|
+
>>> asyncio.run(get_numbers())
|
578
|
+
10 # Sum of the numbers
|
579
|
+
"""
|
580
|
+
def decorator(func: Callable[..., List[Any]]) -> Callable:
|
581
|
+
if is_coroutine_func(func):
|
582
|
+
@functools.wraps(func)
|
583
|
+
async def async_wrapper(*args, **kwargs) -> Any:
|
584
|
+
values = await func(*args, **kwargs)
|
585
|
+
return functools.reduce(function, values, initial)
|
586
|
+
return async_wrapper
|
587
|
+
else:
|
588
|
+
@functools.wraps(func)
|
589
|
+
def sync_wrapper(*args, **kwargs) -> Any:
|
590
|
+
values = func(*args, **kwargs)
|
591
|
+
return functools.reduce(function, values, initial)
|
592
|
+
return sync_wrapper
|
593
|
+
return decorator
|
594
|
+
|
595
|
+
@staticmethod
|
596
|
+
def max_concurrency(limit: int = 5) -> Callable:
|
597
|
+
"""
|
598
|
+
Decorator to limit the maximum number of concurrent executions of an async function.
|
599
|
+
|
600
|
+
Args:
|
601
|
+
limit (int): The maximum number of concurrent tasks allowed.
|
602
|
+
|
603
|
+
Returns:
|
604
|
+
Callable: A decorated function with limited concurrency.
|
605
|
+
|
606
|
+
Examples:
|
607
|
+
>>> @CallDecorator.max_concurrency(3)
|
608
|
+
... async def process_data(): pass
|
609
|
+
"""
|
610
|
+
def decorator(func: Callable) -> Callable:
|
611
|
+
if not asyncio.iscoroutinefunction(func):
|
612
|
+
raise TypeError("max_concurrency decorator can only be used with async functions.")
|
613
|
+
semaphore = asyncio.Semaphore(limit)
|
614
|
+
|
615
|
+
@functools.wraps(func)
|
616
|
+
async def wrapper(*args, **kwargs):
|
617
|
+
async with semaphore:
|
618
|
+
return await func(*args, **kwargs)
|
619
|
+
|
620
|
+
return wrapper
|
621
|
+
|
622
|
+
return decorator
|
623
|
+
|
624
|
+
@staticmethod
|
625
|
+
def throttle(period: int) -> Callable:
|
626
|
+
"""
|
627
|
+
A static method to create a throttling decorator. This method utilizes the Throttle
|
628
|
+
class to enforce a minimum time period between successive calls of the decorated function.
|
629
|
+
|
630
|
+
Args:
|
631
|
+
period (int): The minimum time period, in seconds, that must elapse between successive
|
632
|
+
calls to the decorated function.
|
633
|
+
|
634
|
+
Returns:
|
635
|
+
Callable: A decorator that applies a throttling mechanism to the decorated function,
|
636
|
+
ensuring that the function is not called more frequently than the specified period.
|
637
|
+
|
638
|
+
Examples:
|
639
|
+
>>> @CallDecorator.throttle(2) # Ensures at least 2 seconds between calls
|
640
|
+
... async def fetch_data(): pass
|
641
|
+
|
642
|
+
This decorator is particularly useful in scenarios like rate-limiting API calls or
|
643
|
+
reducing the frequency of resource-intensive operations.
|
644
|
+
"""
|
645
|
+
return Throttle(period)
|
646
|
+
|
647
|
+
class Throttle:
|
648
|
+
"""
|
649
|
+
A class that provides a throttling mechanism for function calls.
|
165
650
|
|
166
|
-
|
167
|
-
|
651
|
+
When used as a decorator, it ensures that the decorated function can only be called
|
652
|
+
once per specified period. Subsequent calls within this period are delayed to enforce
|
653
|
+
this constraint.
|
168
654
|
|
169
|
-
|
170
|
-
|
655
|
+
Attributes:
|
656
|
+
period (int): The minimum time period (in seconds) between successive calls.
|
171
657
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
>>> asyncio.run(al_call([1, 2, 3], async_square))
|
176
|
-
[1, 4, 9]
|
658
|
+
Methods:
|
659
|
+
__call__: Decorates a synchronous function with throttling.
|
660
|
+
__call_async__: Decorates an asynchronous function with throttling.
|
177
661
|
"""
|
178
|
-
try:
|
179
|
-
lst = to_list(input_=input_, flatten=flatten, dropna=dropna)
|
180
|
-
tasks = [func_(i, **kwargs) for i in lst]
|
181
|
-
return await asyncio.gather(*tasks)
|
182
|
-
except Exception as e:
|
183
|
-
raise ValueError(f"Given function cannot be applied to the input. Error: {e}")
|
184
662
|
|
185
|
-
def
|
186
|
-
|
187
|
-
|
188
|
-
|
663
|
+
def __init__(self, period: int) -> None:
|
664
|
+
"""
|
665
|
+
Initializes a new instance of Throttle.
|
666
|
+
|
667
|
+
Args:
|
668
|
+
period (int): The minimum time period (in seconds) between successive calls.
|
669
|
+
"""
|
670
|
+
self.period = period
|
671
|
+
self.last_called = 0
|
672
|
+
|
673
|
+
def __call__(self, func: Callable[..., Any]) -> Callable[..., Any]:
|
674
|
+
"""
|
675
|
+
Decorates a synchronous function with the throttling mechanism.
|
676
|
+
|
677
|
+
Args:
|
678
|
+
func (Callable[..., Any]): The synchronous function to be throttled.
|
679
|
+
|
680
|
+
Returns:
|
681
|
+
Callable[..., Any]: The throttled synchronous function.
|
682
|
+
"""
|
683
|
+
@functools.wraps(func)
|
684
|
+
def wrapper(*args, **kwargs) -> Any:
|
685
|
+
elapsed = time.time() - self.last_called
|
686
|
+
if elapsed < self.period:
|
687
|
+
time.sleep(self.period - elapsed)
|
688
|
+
self.last_called = time.time()
|
689
|
+
return func(*args, **kwargs)
|
690
|
+
|
691
|
+
return wrapper
|
692
|
+
|
693
|
+
async def __call_async__(self, func: Callable[..., Any]) -> Callable[..., Any]:
|
694
|
+
"""
|
695
|
+
Decorates an asynchronous function with the throttling mechanism.
|
696
|
+
|
697
|
+
Args:
|
698
|
+
func (Callable[..., Any]): The asynchronous function to be throttled.
|
699
|
+
|
700
|
+
Returns:
|
701
|
+
Callable[..., Any]: The throttled asynchronous function.
|
702
|
+
"""
|
703
|
+
@functools.wraps(func)
|
704
|
+
async def wrapper(*args, **kwargs) -> Any:
|
705
|
+
elapsed = time.time() - self.last_called
|
706
|
+
if elapsed < self.period:
|
707
|
+
await asyncio.sleep(self.period - elapsed)
|
708
|
+
self.last_called = time.time()
|
709
|
+
return await func(*args, **kwargs)
|
710
|
+
|
711
|
+
return wrapper
|
712
|
+
|
713
|
+
def _dropna(l: List[Any]) -> List[Any]:
|
189
714
|
"""
|
190
|
-
|
191
|
-
|
192
|
-
This function applies a list of functions to a list of inputs, with each function
|
193
|
-
being applied to its corresponding input element. It asserts that the number of inputs
|
194
|
-
and functions are the same and raises an error if they are not.
|
195
|
-
|
196
|
-
Parameters:
|
197
|
-
input (Union[Any, List[Any]]): The input or list of inputs to be processed.
|
715
|
+
Remove None values from a list.
|
198
716
|
|
199
|
-
|
717
|
+
Args:
|
718
|
+
l (List[Any]): A list potentially containing None values.
|
200
719
|
|
201
|
-
|
720
|
+
Returns:
|
721
|
+
List[Any]: A list with None values removed.
|
202
722
|
|
203
|
-
|
723
|
+
Examples:
|
724
|
+
>>> _dropna([1, None, 3, None])
|
725
|
+
[1, 3]
|
726
|
+
"""
|
727
|
+
return [item for item in l if item is not None]
|
728
|
+
|
729
|
+
def _flatten_list(l: List[Any], dropna: bool = True) -> List[Any]:
|
730
|
+
"""
|
731
|
+
Flatten a nested list, optionally removing None values.
|
204
732
|
|
205
|
-
|
733
|
+
Args:
|
734
|
+
l (List[Any]): A nested list to flatten.
|
735
|
+
dropna (bool): If True, None values are removed. Default is True.
|
206
736
|
|
207
737
|
Returns:
|
208
|
-
List[Any]: A list
|
209
|
-
|
210
|
-
Raises:
|
211
|
-
ValueError: If the number of provided inputs and functions are not the same.
|
738
|
+
List[Any]: A flattened list.
|
212
739
|
|
213
|
-
|
214
|
-
>>>
|
215
|
-
|
216
|
-
>>>
|
217
|
-
[2, 3]
|
740
|
+
Examples:
|
741
|
+
>>> _flatten_list([[1, 2], [3, None]], dropna=True)
|
742
|
+
[1, 2, 3]
|
743
|
+
>>> _flatten_list([[1, [2, None]], 3], dropna=False)
|
744
|
+
[1, 2, None, 3]
|
218
745
|
"""
|
219
|
-
|
220
|
-
|
221
|
-
assert len(input_) == len(func_), "The number of inputs and functions must be the same."
|
222
|
-
|
223
|
-
return to_list(
|
224
|
-
[
|
225
|
-
lcall(input_=inp, func_=f, flatten=flatten, dropna=dropna, **kwargs)
|
226
|
-
for f, inp in zip(func_, input_)
|
227
|
-
]
|
228
|
-
)
|
746
|
+
flattened_list = list(_flatten_list_generator(l, dropna))
|
747
|
+
return _dropna(flattened_list) if dropna else flattened_list
|
229
748
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
) -> List[Any]:
|
749
|
+
def _flatten_list_generator(
|
750
|
+
l: List[Any], dropna: bool = True
|
751
|
+
) -> Generator[Any, None, None]:
|
234
752
|
"""
|
235
|
-
|
753
|
+
Generator for flattening a nested list.
|
236
754
|
|
237
|
-
|
238
|
-
|
239
|
-
|
755
|
+
Args:
|
756
|
+
l (List[Any]): A nested list to flatten.
|
757
|
+
dropna (bool): If True, None values are omitted. Default is True.
|
240
758
|
|
241
|
-
|
242
|
-
|
759
|
+
Yields:
|
760
|
+
Generator[Any, None, None]: A generator yielding flattened elements.
|
243
761
|
|
244
|
-
|
762
|
+
Examples:
|
763
|
+
>>> list(_flatten_list_generator([[1, [2, None]], 3], dropna=False))
|
764
|
+
[1, 2, None, 3]
|
765
|
+
"""
|
766
|
+
for i in l:
|
767
|
+
if isinstance(i, list):
|
768
|
+
yield from _flatten_list_generator(i, dropna)
|
769
|
+
else:
|
770
|
+
yield i
|
245
771
|
|
246
|
-
flatten_dict (bool, optional): Whether to flatten dictionaries in the input. Defaults to False.
|
247
772
|
|
248
|
-
|
773
|
+
def _custom_error_handler(error: Exception, error_map: Dict[type, Callable]) -> None:
|
774
|
+
"""
|
775
|
+
Handle errors based on a given error mapping.
|
776
|
+
|
777
|
+
Args:
|
778
|
+
error (Exception): The error to handle.
|
779
|
+
error_map (Dict[type, Callable]): A dictionary mapping error types to handler functions.
|
780
|
+
|
781
|
+
Examples:
|
782
|
+
>>> def handle_value_error(e): print("ValueError occurred")
|
783
|
+
>>> custom_error_handler(ValueError(), {ValueError: handle_value_error})
|
784
|
+
ValueError occurred
|
785
|
+
"""
|
786
|
+
handler = error_map.get(type(error))
|
787
|
+
if handler:
|
788
|
+
handler(error)
|
789
|
+
else:
|
790
|
+
logging.error(f"Unhandled error: {error}")
|
791
|
+
|
792
|
+
async def _call_handler(
|
793
|
+
func: Callable, *args, error_map: Dict[type, Callable] = None,
|
794
|
+
**kwargs
|
795
|
+
) -> Any:
|
796
|
+
"""
|
797
|
+
Call a function with error handling, supporting both synchronous and asynchronous functions.
|
249
798
|
|
250
|
-
|
799
|
+
Args:
|
800
|
+
func (Callable): The function to call.
|
801
|
+
*args: Positional arguments to pass to the function.
|
802
|
+
error_map (Dict[type, Callable], optional): A dictionary mapping error types to handler functions.
|
803
|
+
**kwargs: Keyword arguments to pass to the function.
|
251
804
|
|
252
805
|
Returns:
|
253
|
-
|
806
|
+
Any: The result of the function call.
|
254
807
|
|
255
808
|
Raises:
|
256
|
-
|
257
|
-
|
258
|
-
Example:
|
259
|
-
>>> async def async_add_one(x):
|
260
|
-
... return x + 1
|
261
|
-
>>> asyncio.run(am_call([1, 2], [async_add_one, async_add_one]))
|
262
|
-
[2, 3]
|
263
|
-
"""
|
264
|
-
input = to_list(input_=input_, flatten=flatten, dropna=dropna)
|
265
|
-
func_ = to_list(func_)
|
266
|
-
assert len(input) == len(func_), "Input and function counts must match."
|
267
|
-
|
268
|
-
tasks = [
|
269
|
-
alcall(input_=inp, func_=f, flatten=flatten, dropna=dropna, **kwargs)
|
270
|
-
for f, inp in zip(func_, input)
|
271
|
-
]
|
272
|
-
|
273
|
-
out = await asyncio.gather(*tasks)
|
274
|
-
return to_list(out, flat=True)
|
809
|
+
Exception: Propagates any exceptions not handled by the error_map.
|
275
810
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
811
|
+
Examples:
|
812
|
+
>>> async def async_add(x, y): return x + y
|
813
|
+
>>> asyncio.run(call_handler(async_add, 1, 2))
|
814
|
+
3
|
280
815
|
"""
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
816
|
+
try:
|
817
|
+
if is_coroutine_func(func):
|
818
|
+
# Checking for a running event loop
|
819
|
+
try:
|
820
|
+
loop = asyncio.get_running_loop()
|
821
|
+
except RuntimeError: # No running event loop
|
822
|
+
loop = asyncio.new_event_loop()
|
823
|
+
asyncio.set_event_loop(loop)
|
824
|
+
# Running the coroutine in the new loop
|
825
|
+
result = loop.run_until_complete(func(*args, **kwargs))
|
826
|
+
loop.close()
|
827
|
+
return result
|
828
|
+
|
829
|
+
if loop.is_running():
|
830
|
+
return asyncio.ensure_future(func(*args, **kwargs))
|
831
|
+
else:
|
832
|
+
return await func(*args, **kwargs)
|
833
|
+
else:
|
834
|
+
return func(*args, **kwargs)
|
289
835
|
|
290
|
-
|
836
|
+
except Exception as e:
|
837
|
+
if error_map:
|
838
|
+
_custom_error_handler(e, error_map)
|
839
|
+
else:
|
840
|
+
logging.error(f"Error in call_handler: {e}")
|
841
|
+
raise
|
291
842
|
|
292
|
-
flatten_dict (bool, optional): Whether to flatten dictionaries in the input. Defaults to False.
|
293
843
|
|
294
|
-
|
844
|
+
async def _alcall(
|
845
|
+
input: Any, func: Callable, flatten: bool = False, **kwargs
|
846
|
+
)-> List[Any]:
|
847
|
+
"""
|
848
|
+
Asynchronously apply a function to each element in the input.
|
295
849
|
|
296
|
-
|
850
|
+
Args:
|
851
|
+
input (Any): The input to process.
|
852
|
+
func (Callable): The function to apply.
|
853
|
+
flatten (bool, optional): Whether to flatten the result. Default is False.
|
854
|
+
**kwargs: Keyword arguments to pass to the function.
|
297
855
|
|
298
856
|
Returns:
|
299
|
-
List[Any]: A list of results after applying
|
857
|
+
List[Any]: A list of results after asynchronously applying the function.
|
300
858
|
|
301
|
-
|
302
|
-
>>> def square(x):
|
303
|
-
|
304
|
-
|
305
|
-
[[1], [4], [9]]
|
859
|
+
Examples:
|
860
|
+
>>> async def square(x): return x * x
|
861
|
+
>>> asyncio.run(alcall([1, 2, 3], square))
|
862
|
+
[1, 4, 9]
|
306
863
|
"""
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
864
|
+
lst = to_list(input=input)
|
865
|
+
tasks = [_call_handler(func, i, **kwargs) for i in lst]
|
866
|
+
outs = await asyncio.gather(*tasks)
|
867
|
+
return to_list(outs, flatten=flatten)
|
868
|
+
|
869
|
+
|
870
|
+
async def _tcall(
|
871
|
+
func: Callable, *args, delay: float = 0, err_msg: Optional[str] = None,
|
872
|
+
ignore_err: bool = False, timing: bool = False,
|
873
|
+
default: Any = None, timeout: Optional[float] = None, **kwargs
|
874
|
+
) -> Any:
|
318
875
|
"""
|
319
|
-
Asynchronously
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
876
|
+
Asynchronously call a function with optional delay, timeout, and error handling.
|
877
|
+
|
878
|
+
Args:
|
879
|
+
func (Callable): The function to call.
|
880
|
+
*args: Positional arguments to pass to the function.
|
881
|
+
delay (float): Delay before calling the function, in seconds.
|
882
|
+
err_msg (Optional[str]): Custom error message.
|
883
|
+
ignore_err (bool): If True, ignore errors and return default.
|
884
|
+
timing (bool): If True, return a tuple (result, duration).
|
885
|
+
default (Any): Default value to return on error.
|
886
|
+
timeout (Optional[float]): Timeout for the function call, in seconds.
|
887
|
+
**kwargs: Keyword arguments to pass to the function.
|
329
888
|
|
330
|
-
|
331
|
-
|
332
|
-
flat (bool, optional): Whether the output list should be flattened. Defaults to True.
|
333
|
-
|
334
|
-
dropna (bool, optional): Whether to drop None values during flattening. Defaults to True.
|
889
|
+
Returns:
|
890
|
+
Any: The result of the function call, or (result, duration) if timing is True.
|
335
891
|
|
336
|
-
|
337
|
-
>>> async def
|
338
|
-
|
339
|
-
|
340
|
-
[[1, 4, 9]]
|
892
|
+
Examples:
|
893
|
+
>>> async def example_func(x): return x
|
894
|
+
>>> asyncio.run(tcall(example_func, 5, timing=True))
|
895
|
+
(5, duration)
|
341
896
|
"""
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
897
|
+
start_time = time.time()
|
898
|
+
try:
|
899
|
+
await asyncio.sleep(delay)
|
900
|
+
# Apply timeout to the function call
|
901
|
+
if timeout is not None:
|
902
|
+
result = await asyncio.wait_for(func(*args, **kwargs), timeout)
|
903
|
+
else:
|
904
|
+
if is_coroutine_func(func):
|
905
|
+
return await func( *args, **kwargs)
|
906
|
+
return func(*args, **kwargs)
|
907
|
+
duration = time.time() - start_time
|
908
|
+
return (result, duration) if timing else result
|
909
|
+
except asyncio.TimeoutError as e:
|
910
|
+
err_msg = f"{err_msg} Error: {e}" if err_msg else f"An error occurred: {e}"
|
911
|
+
print(err_msg)
|
912
|
+
if ignore_err:
|
913
|
+
return (default, time.time() - start_time) if timing else default
|
914
|
+
else:
|
915
|
+
raise e # Re-raise the timeout exception
|
916
|
+
except Exception as e:
|
917
|
+
err_msg = f"{err_msg} Error: {e}" if err_msg else f"An error occurred: {e}"
|
918
|
+
print(err_msg)
|
919
|
+
if ignore_err:
|
920
|
+
return (default, time.time() - start_time) if timing else default
|
921
|
+
else:
|
922
|
+
raise e
|
923
|
+
|