lionagi 0.0.201__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/_services/anthropic.py +79 -1
- lionagi/_services/base_service.py +1 -1
- lionagi/_services/services.py +61 -25
- lionagi/_services/transformers.py +46 -0
- lionagi/agents/__init__.py +0 -0
- lionagi/configs/oai_configs.py +1 -1
- lionagi/configs/openrouter_configs.py +1 -1
- lionagi/core/__init__.py +3 -7
- lionagi/core/branch/__init__.py +0 -0
- lionagi/core/branch/branch.py +589 -0
- lionagi/core/branch/branch_manager.py +139 -0
- lionagi/core/branch/cluster.py +1 -0
- lionagi/core/branch/conversation.py +484 -0
- lionagi/core/core_util.py +59 -0
- lionagi/core/flow/__init__.py +0 -0
- lionagi/core/flow/flow.py +19 -0
- lionagi/core/instruction_set/__init__.py +0 -0
- lionagi/core/instruction_set/instruction_set.py +343 -0
- lionagi/core/messages/__init__.py +0 -0
- lionagi/core/messages/messages.py +176 -0
- lionagi/core/sessions/__init__.py +0 -0
- lionagi/core/sessions/session.py +428 -0
- lionagi/models/__init__.py +0 -0
- lionagi/models/base_model.py +0 -0
- lionagi/models/imodel.py +53 -0
- lionagi/schema/data_logger.py +75 -155
- lionagi/tests/test_utils/test_call_util.py +658 -657
- lionagi/tools/tool_manager.py +121 -188
- lionagi/utils/__init__.py +5 -10
- lionagi/utils/call_util.py +667 -585
- lionagi/utils/io_util.py +3 -0
- lionagi/utils/nested_util.py +17 -211
- lionagi/utils/pd_util.py +57 -0
- lionagi/utils/sys_util.py +220 -184
- lionagi/utils/url_util.py +55 -0
- lionagi/version.py +1 -1
- {lionagi-0.0.201.dist-info → lionagi-0.0.204.dist-info}/METADATA +12 -8
- {lionagi-0.0.201.dist-info → lionagi-0.0.204.dist-info}/RECORD +47 -32
- lionagi/core/branch.py +0 -193
- lionagi/core/conversation.py +0 -341
- lionagi/core/flow.py +0 -8
- lionagi/core/instruction_set.py +0 -150
- lionagi/core/messages.py +0 -243
- lionagi/core/sessions.py +0 -474
- /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/core/{flow_util.py → flow/flow_util.py} +0 -0
- {lionagi-0.0.201.dist-info → lionagi-0.0.204.dist-info}/LICENSE +0 -0
- {lionagi-0.0.201.dist-info → lionagi-0.0.204.dist-info}/WHEEL +0 -0
- {lionagi-0.0.201.dist-info → lionagi-0.0.204.dist-info}/top_level.txt +0 -0
lionagi/utils/call_util.py
CHANGED
@@ -1,735 +1,648 @@
|
|
1
|
-
from collections import OrderedDict
|
2
1
|
import asyncio
|
3
|
-
import functools
|
4
|
-
|
5
|
-
import concurrent.futures
|
2
|
+
import functools
|
3
|
+
import logging
|
6
4
|
import time
|
7
|
-
from typing import Any, Callable,
|
8
|
-
|
9
|
-
from
|
5
|
+
from typing import Any, Callable, Generator, Iterable, List, Dict, Optional, Tuple
|
6
|
+
|
7
|
+
from aiocache import cached
|
10
8
|
|
11
9
|
|
12
|
-
def
|
13
|
-
input_: Any, func_: Callable, flatten: bool = False,
|
14
|
-
dropna: bool = False, **kwargs) -> List[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
14
|
Args:
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
dropna (bool, optional): If True, drops None values from the list.
|
23
|
-
**kwargs: Additional keyword arguments to pass to the function.
|
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.
|
24
18
|
|
25
19
|
Returns:
|
26
|
-
List[Any]:
|
27
|
-
|
28
|
-
Raises:
|
29
|
-
ValueError: If the function cannot be applied to an element in the list.
|
20
|
+
List[Any]: The input converted to a list.
|
30
21
|
|
31
22
|
Examples:
|
32
|
-
>>>
|
33
|
-
[2, 3
|
34
|
-
>>>
|
35
|
-
[
|
23
|
+
>>> to_list([1, [2, None], 3], flatten=True, dropna=True)
|
24
|
+
[1, 2, 3]
|
25
|
+
>>> to_list("hello", flatten=False)
|
26
|
+
["hello"]
|
36
27
|
"""
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
42
40
|
|
43
|
-
|
44
|
-
|
45
|
-
dropna: bool = False, **kwargs
|
41
|
+
def lcall(
|
42
|
+
input: Any, func: Callable, flatten: bool = False,
|
43
|
+
dropna: bool = False, **kwargs
|
44
|
+
) -> List[Any]:
|
46
45
|
"""
|
47
|
-
|
46
|
+
Apply a function to each element of the input list, with options to flatten and drop None values.
|
48
47
|
|
49
48
|
Args:
|
50
|
-
|
51
|
-
|
52
|
-
flatten (bool
|
53
|
-
dropna (bool
|
54
|
-
**kwargs: Additional keyword arguments to pass to
|
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.
|
55
54
|
|
56
55
|
Returns:
|
57
|
-
List[Any]: A list
|
58
|
-
|
59
|
-
Raises:
|
60
|
-
ValueError: If the function cannot be applied to an element in the list.
|
56
|
+
List[Any]: A list of results after applying the function.
|
61
57
|
|
62
58
|
Examples:
|
63
|
-
>>>
|
64
|
-
|
65
|
-
[2,
|
66
|
-
>>> asyncio.run(alcall([1, None, 3], add_one, dropna=True))
|
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)
|
67
62
|
[2, 4]
|
68
63
|
"""
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
# timed call
|
80
|
-
async def tcall(input_: Any, func: Callable, sleep: float = 0.1,
|
81
|
-
message: Optional[str] = None, ignore_error: bool = False,
|
82
|
-
include_timing: bool = False, **kwargs
|
83
|
-
) -> Union[Any, Tuple[Any, float]]:
|
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:
|
84
73
|
"""
|
85
|
-
|
74
|
+
Check if the given function is a coroutine function.
|
86
75
|
|
87
76
|
Args:
|
88
|
-
|
89
|
-
func (Callable): The (async) function to be called.
|
90
|
-
sleep (float, optional): Delay before the function call in seconds.
|
91
|
-
message (Optional[str], optional): Custom message for error handling.
|
92
|
-
ignore_error (bool, optional): If False, re-raises the caught exception.
|
93
|
-
include_timing (bool, optional): If True, returns execution duration.
|
94
|
-
**kwargs: Additional keyword arguments to pass to the function.
|
77
|
+
func (Callable): The function to check.
|
95
78
|
|
96
79
|
Returns:
|
97
|
-
|
98
|
-
|
99
|
-
Raises:
|
100
|
-
Exception: If the function raises an exception and ignore_error is False.
|
80
|
+
bool: True if the function is a coroutine function, False otherwise.
|
101
81
|
|
102
82
|
Examples:
|
103
|
-
>>> async def
|
104
|
-
>>>
|
105
|
-
|
106
|
-
|
107
|
-
(
|
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
|
108
89
|
"""
|
109
|
-
|
110
|
-
start_time = time.time()
|
111
|
-
try:
|
112
|
-
await asyncio.sleep(sleep)
|
113
|
-
result = await func(input_, **kwargs)
|
114
|
-
duration = time.time() - start_time
|
115
|
-
return (result, duration) if include_timing else result
|
116
|
-
except Exception as e:
|
117
|
-
handle_error(e)
|
90
|
+
return asyncio.iscoroutinefunction(func)
|
118
91
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
duration = time.time() - start_time
|
125
|
-
return (result, duration) if include_timing else result
|
126
|
-
except Exception as e:
|
127
|
-
handle_error(e)
|
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.
|
128
97
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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.
|
134
103
|
|
135
|
-
|
136
|
-
|
137
|
-
else:
|
138
|
-
return sync_call()
|
104
|
+
Returns:
|
105
|
+
List[Any]: A list of results after asynchronously applying the function.
|
139
106
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
dropna: bool = False,
|
145
|
-
**kwargs) -> List[Any]:
|
107
|
+
Examples:
|
108
|
+
>>> async def square(x): return x * x
|
109
|
+
>>> asyncio.run(alcall([1, 2, 3], square))
|
110
|
+
[1, 4, 9]
|
146
111
|
"""
|
147
|
-
|
148
|
-
|
149
|
-
|
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.
|
150
122
|
|
151
123
|
Args:
|
152
|
-
|
153
|
-
|
154
|
-
explode (bool, optional):
|
155
|
-
|
156
|
-
dropna (bool, optional): If True, drops None values from the list.
|
157
|
-
**kwargs: Additional keyword arguments for the function calls.
|
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.
|
158
128
|
|
159
129
|
Returns:
|
160
|
-
List[Any]:
|
130
|
+
List[Any]: A list of results after applying the function(s).
|
161
131
|
|
162
132
|
Examples:
|
163
|
-
>>> async def
|
164
|
-
>>>
|
165
|
-
|
166
|
-
|
167
|
-
>>> asyncio.run(mcall([1, 2, 3], [increment, double, increment], explode=True))
|
168
|
-
[[2, 4, 4], [3, 5, 5], [4, 6, 6]]
|
133
|
+
>>> async def add_one(x): return x + 1
|
134
|
+
>>> asyncio.run(mcall([1, 2, 3], add_one))
|
135
|
+
[2, 3, 4]
|
136
|
+
|
169
137
|
"""
|
138
|
+
input_ = to_list(input, dropna=True)
|
139
|
+
funcs_ = to_list(func, dropna=True)
|
140
|
+
|
170
141
|
if explode:
|
171
|
-
|
142
|
+
tasks = [
|
143
|
+
_alcall(input_, f, flatten=True, **kwargs)
|
144
|
+
for f in funcs_
|
145
|
+
]
|
146
|
+
return await asyncio.gather(*tasks)
|
172
147
|
else:
|
173
|
-
|
174
|
-
|
175
|
-
|
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)
|
155
|
+
|
156
|
+
async def bcall(input: Any, func: Callable, batch_size: int, **kwargs) -> List[Any]:
|
176
157
|
"""
|
177
|
-
|
158
|
+
Asynchronously call a function on batches of inputs.
|
178
159
|
|
179
160
|
Args:
|
180
|
-
|
181
|
-
func (Callable
|
182
|
-
batch_size (int): The
|
183
|
-
**kwargs:
|
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.
|
184
165
|
|
185
166
|
Returns:
|
186
|
-
List[Any]: A list of results
|
187
|
-
|
188
|
-
Raises:
|
189
|
-
Exception: If an exception occurs during batch processing.
|
167
|
+
List[Any]: A list of results after applying the function in batches.
|
190
168
|
|
191
169
|
Examples:
|
192
|
-
>>> async def
|
193
|
-
>>> asyncio.run(bcall([1, 2, 3, 4],
|
194
|
-
[
|
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]
|
195
173
|
"""
|
196
|
-
|
197
|
-
async def process_batch(batch: List[Any]) -> List[Any]:
|
198
|
-
if asyncio.iscoroutinefunction(func):
|
199
|
-
# Process asynchronously if the function is a coroutine
|
200
|
-
return await asyncio.gather(*(func(item, **kwargs) for item in batch))
|
201
|
-
else:
|
202
|
-
# Process synchronously otherwise
|
203
|
-
return [func(item, **kwargs) for item in batch]
|
204
|
-
|
205
174
|
results = []
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
# Handle exceptions or log errors here if needed
|
213
|
-
raise e
|
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
|
+
|
214
181
|
return results
|
215
182
|
|
216
|
-
async def
|
217
|
-
|
218
|
-
|
219
|
-
|
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:
|
220
188
|
"""
|
221
|
-
|
189
|
+
Asynchronously call a function with optional delay, timeout, and error handling.
|
222
190
|
|
223
191
|
Args:
|
224
|
-
func (Callable
|
192
|
+
func (Callable): The function to call.
|
225
193
|
*args: Positional arguments to pass to the function.
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
default (
|
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.
|
231
200
|
**kwargs: Keyword arguments to pass to the function.
|
232
201
|
|
233
202
|
Returns:
|
234
|
-
Any: The result of the function call,
|
235
|
-
|
236
|
-
Raises:
|
237
|
-
Exception: If the function raises an exception beyond the specified retries.
|
203
|
+
Any: The result of the function call, or (result, duration) if timing is True.
|
238
204
|
|
239
205
|
Examples:
|
240
|
-
>>> async def
|
241
|
-
>>> asyncio.run(
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
future.cancel()
|
256
|
-
raise asyncio.TimeoutError("Function call timed out")
|
206
|
+
>>> async def example_func(x): return x
|
207
|
+
>>> asyncio.run(tcall(example_func, 5, timing=True))
|
208
|
+
(5, duration)
|
209
|
+
"""
|
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)
|
257
221
|
|
258
|
-
|
259
|
-
|
222
|
+
def sync_call() -> Tuple[Any, float]:
|
223
|
+
start_time = time.time()
|
260
224
|
try:
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
225
|
+
time.sleep(delay)
|
226
|
+
result = func(*args, **kwargs)
|
227
|
+
duration = time.time() - start_time
|
228
|
+
return (result, duration) if timing else result
|
265
229
|
except Exception as e:
|
266
|
-
|
267
|
-
if default is not None:
|
268
|
-
return default
|
269
|
-
raise e
|
270
|
-
await asyncio.sleep(delay)
|
271
|
-
delay *= backoff_factor
|
230
|
+
handle_error(e)
|
272
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
|
273
237
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
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:
|
248
|
+
"""
|
249
|
+
Asynchronously retry a function call with exponential backoff.
|
282
250
|
|
283
|
-
|
284
|
-
|
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.
|
285
260
|
|
286
|
-
|
287
|
-
|
288
|
-
"""
|
261
|
+
Returns:
|
262
|
+
Any: The result of the function call, or default if retries are exhausted.
|
289
263
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
async def async_wrapper(*args, **kwargs) -> Any:
|
298
|
-
return await cached_async(*args, **kwargs)
|
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
|
299
271
|
|
300
|
-
|
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")
|
301
290
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
@ft.wraps(func)
|
309
|
-
def sync_wrapper(*args, **kwargs) -> Any:
|
310
|
-
return cached_sync(*args, **kwargs)
|
291
|
+
class CallDecorator:
|
292
|
+
|
293
|
+
"""
|
294
|
+
Call Decorators
|
295
|
+
"""
|
311
296
|
|
312
|
-
return sync_wrapper
|
313
297
|
|
314
298
|
@staticmethod
|
315
299
|
def timeout(timeout: int) -> Callable:
|
316
300
|
"""
|
317
|
-
Decorator to apply a timeout to
|
301
|
+
Decorator to apply a timeout to an asynchronous function.
|
318
302
|
|
319
303
|
Args:
|
320
|
-
timeout (int):
|
304
|
+
timeout (int): Timeout duration in seconds.
|
321
305
|
|
322
306
|
Returns:
|
323
|
-
Callable: A decorated function with
|
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
|
324
313
|
"""
|
325
314
|
def decorator(func: Callable[..., Any]) -> Callable:
|
326
|
-
@
|
327
|
-
async def
|
315
|
+
@functools.wraps(func)
|
316
|
+
async def wrapper(*args, **kwargs) -> Any:
|
328
317
|
return await rcall(func, *args, timeout=timeout, **kwargs)
|
329
|
-
|
330
|
-
@ft.wraps(func)
|
331
|
-
def sync_wrapper(*args, **kwargs) -> Any:
|
332
|
-
return asyncio.run(rcall(func, *args, timeout=timeout, **kwargs))
|
333
|
-
|
334
|
-
if asyncio.iscoroutinefunction(func):
|
335
|
-
return async_wrapper
|
336
|
-
else:
|
337
|
-
return sync_wrapper
|
318
|
+
return wrapper
|
338
319
|
return decorator
|
339
320
|
|
340
321
|
@staticmethod
|
341
|
-
def retry(
|
322
|
+
def retry(
|
323
|
+
retries: int = 3, delay: float = 2.0, backoff_factor: float = 2.0
|
324
|
+
) -> Callable:
|
342
325
|
"""
|
343
|
-
Decorator to
|
326
|
+
Decorator to retry an asynchronous function with exponential backoff.
|
344
327
|
|
345
328
|
Args:
|
346
|
-
retries (int):
|
347
|
-
initial_delay (float): Initial delay in seconds
|
348
|
-
backoff_factor (float): Factor by which
|
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.
|
349
332
|
|
350
333
|
Returns:
|
351
|
-
Callable: A decorated function with
|
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
|
352
340
|
"""
|
353
341
|
def decorator(func: Callable[..., Any]) -> Callable:
|
354
|
-
@
|
355
|
-
async def
|
356
|
-
return await rcall(
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
if asyncio.iscoroutinefunction(func):
|
363
|
-
return async_wrapper
|
364
|
-
else:
|
365
|
-
return sync_wrapper
|
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
|
366
349
|
return decorator
|
367
350
|
|
368
351
|
@staticmethod
|
369
352
|
def default(default_value: Any) -> Callable:
|
370
|
-
"""
|
371
|
-
Decorator to apply a default value mechanism to a function.
|
372
|
-
|
373
|
-
Args:
|
374
|
-
default (Any): The default value to return in case the function execution fails.
|
375
|
-
|
376
|
-
Returns:
|
377
|
-
Callable: A decorated function that returns a default value on failure.
|
378
|
-
"""
|
379
353
|
def decorator(func: Callable[..., Any]) -> Callable:
|
380
|
-
@
|
381
|
-
async def
|
354
|
+
@functools.wraps(func)
|
355
|
+
async def wrapper(*args, **kwargs) -> Any:
|
382
356
|
return await rcall(func, *args, default=default_value, **kwargs)
|
383
|
-
|
384
|
-
@ft.wraps(func)
|
385
|
-
def sync_wrapper(*args, **kwargs) -> Any:
|
386
|
-
return asyncio.run(rcall(func, *args, default=default_value, **kwargs))
|
387
|
-
|
388
|
-
if asyncio.iscoroutinefunction(func):
|
389
|
-
return async_wrapper
|
390
|
-
else:
|
391
|
-
return sync_wrapper
|
357
|
+
return wrapper
|
392
358
|
return decorator
|
393
359
|
|
394
360
|
@staticmethod
|
395
361
|
def throttle(period: int) -> Callable:
|
396
362
|
"""
|
397
|
-
A
|
363
|
+
A static method to create a throttling decorator using the Throttle class.
|
398
364
|
|
399
365
|
Args:
|
400
|
-
period (int): The minimum time period (in seconds) between successive calls
|
366
|
+
period (int): The minimum time period (in seconds) between successive calls.
|
401
367
|
|
402
368
|
Returns:
|
403
|
-
Callable: A decorator that applies a throttling mechanism to the decorated
|
404
|
-
|
405
|
-
Usage:
|
406
|
-
@throttle(2)
|
407
|
-
def my_function():
|
408
|
-
# Function implementation
|
409
|
-
|
410
|
-
@throttle(2)
|
411
|
-
async def my_async_function():
|
412
|
-
# Async function implementation
|
369
|
+
Callable: A decorator that applies a throttling mechanism to the decorated
|
370
|
+
function.
|
413
371
|
"""
|
414
|
-
|
415
|
-
throttle_decorator = Throttle(period)
|
416
|
-
if asyncio.iscoroutinefunction(func):
|
417
|
-
return throttle_decorator.__call_async__(func)
|
418
|
-
else:
|
419
|
-
return throttle_decorator(func)
|
420
|
-
return decorator
|
372
|
+
return Throttle(period)
|
421
373
|
|
422
374
|
@staticmethod
|
423
|
-
def
|
375
|
+
def map(function: Callable[[Any], Any]) -> Callable:
|
424
376
|
"""
|
425
|
-
Decorator
|
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.
|
426
382
|
|
427
383
|
Args:
|
428
|
-
|
429
|
-
postprocess (Callable[..., Any]): A function to postprocess the result.
|
384
|
+
function (Callable[[Any], Any]): A function to apply to each element of the list.
|
430
385
|
|
431
386
|
Returns:
|
432
|
-
Callable: A
|
433
|
-
"""
|
387
|
+
Callable: A decorated function that maps the provided function over its results.
|
434
388
|
|
435
|
-
|
436
|
-
@
|
437
|
-
async def
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
result = func(*preprocessed_args, **preprocessed_kwargs)
|
448
|
-
return postprocess(result)
|
449
|
-
|
450
|
-
if asyncio.iscoroutinefunction(func):
|
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]
|
451
401
|
return async_wrapper
|
452
|
-
else:
|
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]
|
453
407
|
return sync_wrapper
|
454
|
-
|
455
408
|
return decorator
|
456
409
|
|
457
410
|
@staticmethod
|
458
|
-
def
|
411
|
+
def compose(*functions: Callable[[Any], Any]) -> Callable:
|
459
412
|
"""
|
460
|
-
Decorator factory
|
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.
|
461
415
|
|
462
416
|
Args:
|
463
|
-
|
417
|
+
*functions: Variable length list of functions to compose.
|
464
418
|
|
465
419
|
Returns:
|
466
|
-
Callable:
|
420
|
+
Callable: A new function that is the composition of the given functions.
|
467
421
|
"""
|
468
|
-
def decorator(func: Callable
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
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.")
|
474
447
|
return decorator
|
475
448
|
|
476
449
|
@staticmethod
|
477
|
-
def
|
450
|
+
def pre_post_process(
|
451
|
+
preprocess: Callable[..., Any], postprocess: Callable[..., Any]
|
452
|
+
) -> Callable:
|
478
453
|
"""
|
479
|
-
Decorator
|
454
|
+
Decorator that applies preprocessing and postprocessing functions to the arguments
|
455
|
+
and result of an asynchronous function.
|
480
456
|
|
481
457
|
Args:
|
482
|
-
|
458
|
+
preprocess (Callable[..., Any]): A function to preprocess each argument.
|
459
|
+
postprocess (Callable[..., Any]): A function to postprocess the result.
|
483
460
|
|
484
461
|
Returns:
|
485
|
-
Callable:
|
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
|
486
469
|
"""
|
487
|
-
def decorator(func: Callable[...,
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
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
|
+
|
493
488
|
return decorator
|
494
489
|
|
495
490
|
@staticmethod
|
496
|
-
def
|
491
|
+
def cache(func: Callable, ttl=600, maxsize=None) -> Callable:
|
497
492
|
"""
|
498
|
-
Decorator
|
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.
|
499
496
|
|
500
497
|
Args:
|
501
|
-
|
502
|
-
initial (Any): Initial value for reduction.
|
498
|
+
func (Callable): The function (can be sync or async) whose results need to be cached.
|
503
499
|
|
504
500
|
Returns:
|
505
|
-
Callable:
|
501
|
+
Callable: A decorated function with caching applied.
|
506
502
|
"""
|
507
|
-
def decorator(func: Callable[..., List[Any]]) -> Callable:
|
508
|
-
@ft.wraps(func)
|
509
|
-
def wrapper(*args, **kwargs) -> Any:
|
510
|
-
values = func(*args, **kwargs)
|
511
|
-
return ft.reduce(function, values, initial)
|
512
|
-
return wrapper
|
513
|
-
return decorator
|
514
503
|
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
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)
|
520
513
|
|
521
|
-
|
522
|
-
*functions: Variable length list of functions to compose.
|
514
|
+
return async_wrapper
|
523
515
|
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
raise ValueError(f"Error in function {function.__name__}: {e}")
|
536
|
-
return value
|
537
|
-
return wrapper
|
538
|
-
return decorator
|
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
|
539
527
|
|
540
528
|
@staticmethod
|
541
|
-
def
|
529
|
+
def filter(predicate: Callable[[Any], bool]) -> Callable:
|
542
530
|
"""
|
543
|
-
Decorator
|
531
|
+
Decorator that filters the results of an asynchronous function based on a predicate
|
532
|
+
function.
|
544
533
|
|
545
534
|
Args:
|
546
|
-
|
535
|
+
predicate (Callable[[Any], bool]): A function that returns True for items to keep.
|
547
536
|
|
548
537
|
Returns:
|
549
|
-
Callable: A
|
550
|
-
"""
|
551
|
-
def decorator(function: Callable) -> Callable:
|
552
|
-
cache = OrderedDict()
|
553
|
-
|
554
|
-
@ft.wraps(function)
|
555
|
-
def memorized_function(*args):
|
556
|
-
if args in cache:
|
557
|
-
cache.move_to_end(args) # Move the recently accessed item to the end
|
558
|
-
return cache[args]
|
559
|
-
|
560
|
-
if len(cache) >= maxsize:
|
561
|
-
cache.popitem(last=False) # Remove oldest cache entry
|
538
|
+
Callable: A decorated function that filters its results based on the predicate.
|
562
539
|
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
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
|
569
559
|
return decorator
|
570
560
|
|
571
561
|
@staticmethod
|
572
|
-
def
|
562
|
+
def reduce(function: Callable[[Any, Any], Any], initial: Any) -> Callable:
|
573
563
|
"""
|
574
|
-
Decorator
|
564
|
+
Decorator that reduces the results of an asynchronous function to a single value using
|
565
|
+
the specified reduction function.
|
575
566
|
|
576
567
|
Args:
|
577
|
-
|
568
|
+
function (Callable[[Any, Any], Any]): A reduction function to apply.
|
569
|
+
initial (Any): The initial value for the reduction.
|
578
570
|
|
579
571
|
Returns:
|
580
|
-
Callable: A
|
581
|
-
"""
|
582
|
-
def decorator(func: Callable) -> Callable:
|
583
|
-
@ft.wraps(func)
|
584
|
-
def wrapper(*args, **kwargs) -> Any:
|
585
|
-
value = func(*args, **kwargs)
|
586
|
-
return _process_value(value, config)
|
587
|
-
|
588
|
-
return wrapper
|
572
|
+
Callable: A decorated function that reduces its results to a single value.
|
589
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
|
590
593
|
return decorator
|
591
|
-
|
594
|
+
|
592
595
|
@staticmethod
|
593
|
-
def max_concurrency(limit: int=5):
|
596
|
+
def max_concurrency(limit: int = 5) -> Callable:
|
594
597
|
"""
|
595
598
|
Decorator to limit the maximum number of concurrent executions of an async function.
|
596
|
-
|
599
|
+
|
597
600
|
Args:
|
598
601
|
limit (int): The maximum number of concurrent tasks allowed.
|
599
|
-
"""
|
600
602
|
|
601
|
-
|
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.")
|
602
613
|
semaphore = asyncio.Semaphore(limit)
|
603
614
|
|
604
|
-
@
|
615
|
+
@functools.wraps(func)
|
605
616
|
async def wrapper(*args, **kwargs):
|
606
617
|
async with semaphore:
|
607
618
|
return await func(*args, **kwargs)
|
608
619
|
|
609
620
|
return wrapper
|
610
|
-
|
621
|
+
|
611
622
|
return decorator
|
612
623
|
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
config: A dictionary with optional keys 'log' and 'default'.
|
619
|
-
|
620
|
-
Returns:
|
621
|
-
The original value or the default value from config if value is an exception.
|
622
|
-
|
623
|
-
Examples:
|
624
|
-
>>> handle_error(ValueError("An error"), {'log': True, 'default': 'default_value'})
|
625
|
-
Error: An error
|
626
|
-
'default_value'
|
627
|
-
"""
|
628
|
-
if isinstance(value, Exception):
|
629
|
-
if config.get('log', True):
|
630
|
-
print(f"Error: {value}") # Replace with appropriate logging mechanism
|
631
|
-
return config.get('default', None)
|
632
|
-
return value
|
633
|
-
|
634
|
-
def _validate_type(value: Any, expected_type: Type) -> Any:
|
635
|
-
"""Validate the type of value, raise TypeError if not expected type.
|
636
|
-
|
637
|
-
Args:
|
638
|
-
value: The value to validate.
|
639
|
-
expected_type: The type that value is expected to be.
|
640
|
-
|
641
|
-
Returns:
|
642
|
-
The original value if it is of the expected type.
|
643
|
-
|
644
|
-
Raises:
|
645
|
-
TypeError: If value is not of the expected type.
|
646
|
-
|
647
|
-
Examples:
|
648
|
-
>>> validate_type(10, int)
|
649
|
-
10
|
650
|
-
>>> validate_type("10", int)
|
651
|
-
Traceback (most recent call last):
|
652
|
-
...
|
653
|
-
TypeError: Invalid type: expected <class 'int'>, got <class 'str'>
|
654
|
-
"""
|
655
|
-
if not isinstance(value, expected_type):
|
656
|
-
raise TypeError(f"Invalid type: expected {expected_type}, got {type(value)}")
|
657
|
-
return value
|
658
|
-
|
659
|
-
def _convert_type(value: Any, target_type: Callable) -> Optional[Any]:
|
660
|
-
"""Convert the type of value to target_type, return None if conversion fails.
|
661
|
-
|
662
|
-
Args:
|
663
|
-
value: The value to convert.
|
664
|
-
target_type: The type to convert value to.
|
665
|
-
|
666
|
-
Returns:
|
667
|
-
The converted value or None if conversion fails.
|
668
|
-
|
669
|
-
Examples:
|
670
|
-
>>> convert_type("10", int)
|
671
|
-
10
|
672
|
-
>>> convert_type("abc", int)
|
673
|
-
Conversion error: invalid literal for int() with base 10: 'abc'
|
674
|
-
None
|
675
|
-
"""
|
676
|
-
try:
|
677
|
-
return target_type(value)
|
678
|
-
except (ValueError, TypeError) as e:
|
679
|
-
print(f"Conversion error: {e}") # Replace with appropriate logging mechanism
|
680
|
-
return None
|
681
|
-
|
682
|
-
def _process_value(value: Any, config: Dict[str, Any]) -> Any:
|
683
|
-
"""
|
684
|
-
Processes a value using a chain of functions defined in config.
|
685
|
-
"""
|
686
|
-
processing_functions = {
|
687
|
-
'handle_error': _handle_error,
|
688
|
-
'validate_type': _validate_type,
|
689
|
-
'convert_type': _convert_type
|
690
|
-
}
|
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.
|
691
629
|
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
if func:
|
696
|
-
value = func(value, func_config)
|
697
|
-
return value
|
698
|
-
except Exception as e:
|
699
|
-
if 'handle_error' in config.keys():
|
700
|
-
func = processing_functions.get('handle_error')
|
701
|
-
return func(e, config['handle_error'])
|
702
|
-
else:
|
703
|
-
raise e
|
630
|
+
Args:
|
631
|
+
period (int): The minimum time period, in seconds, that must elapse between successive
|
632
|
+
calls to the decorated function.
|
704
633
|
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
dropna: bool = False,
|
709
|
-
**kwargs) -> List[Any]:
|
710
|
-
|
711
|
-
input_ = to_list(input_=input_, flatten=flatten, dropna=dropna)
|
712
|
-
funcs = to_list(funcs)
|
713
|
-
assert len(input_) == len(funcs), "The number of inputs and functions must be the same."
|
714
|
-
return to_list(
|
715
|
-
[
|
716
|
-
await alcall(input_=inp, func_=f, flatten=flatten, dropna=dropna, **kwargs)
|
717
|
-
for f, inp in zip(funcs, input_)
|
718
|
-
]
|
719
|
-
)
|
720
|
-
|
721
|
-
async def _explode_call(input_: Union[Any, List[Any]],
|
722
|
-
funcs: Union[Callable, List[Callable]],
|
723
|
-
dropna: bool = False,
|
724
|
-
**kwargs) -> List[Any]:
|
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.
|
725
637
|
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
)
|
638
|
+
Examples:
|
639
|
+
>>> @CallDecorator.throttle(2) # Ensures at least 2 seconds between calls
|
640
|
+
... async def fetch_data(): pass
|
730
641
|
|
731
|
-
|
732
|
-
|
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)
|
733
646
|
|
734
647
|
class Throttle:
|
735
648
|
"""
|
@@ -767,7 +680,7 @@ class Throttle:
|
|
767
680
|
Returns:
|
768
681
|
Callable[..., Any]: The throttled synchronous function.
|
769
682
|
"""
|
770
|
-
@
|
683
|
+
@functools.wraps(func)
|
771
684
|
def wrapper(*args, **kwargs) -> Any:
|
772
685
|
elapsed = time.time() - self.last_called
|
773
686
|
if elapsed < self.period:
|
@@ -787,7 +700,7 @@ class Throttle:
|
|
787
700
|
Returns:
|
788
701
|
Callable[..., Any]: The throttled asynchronous function.
|
789
702
|
"""
|
790
|
-
@
|
703
|
+
@functools.wraps(func)
|
791
704
|
async def wrapper(*args, **kwargs) -> Any:
|
792
705
|
elapsed = time.time() - self.last_called
|
793
706
|
if elapsed < self.period:
|
@@ -796,46 +709,215 @@ class Throttle:
|
|
796
709
|
return await func(*args, **kwargs)
|
797
710
|
|
798
711
|
return wrapper
|
712
|
+
|
713
|
+
def _dropna(l: List[Any]) -> List[Any]:
|
714
|
+
"""
|
715
|
+
Remove None values from a list.
|
716
|
+
|
717
|
+
Args:
|
718
|
+
l (List[Any]): A list potentially containing None values.
|
719
|
+
|
720
|
+
Returns:
|
721
|
+
List[Any]: A list with None values removed.
|
722
|
+
|
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.
|
732
|
+
|
733
|
+
Args:
|
734
|
+
l (List[Any]): A nested list to flatten.
|
735
|
+
dropna (bool): If True, None values are removed. Default is True.
|
736
|
+
|
737
|
+
Returns:
|
738
|
+
List[Any]: A flattened list.
|
739
|
+
|
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]
|
745
|
+
"""
|
746
|
+
flattened_list = list(_flatten_list_generator(l, dropna))
|
747
|
+
return _dropna(flattened_list) if dropna else flattened_list
|
748
|
+
|
749
|
+
def _flatten_list_generator(
|
750
|
+
l: List[Any], dropna: bool = True
|
751
|
+
) -> Generator[Any, None, None]:
|
752
|
+
"""
|
753
|
+
Generator for flattening a nested list.
|
754
|
+
|
755
|
+
Args:
|
756
|
+
l (List[Any]): A nested list to flatten.
|
757
|
+
dropna (bool): If True, None values are omitted. Default is True.
|
758
|
+
|
759
|
+
Yields:
|
760
|
+
Generator[Any, None, None]: A generator yielding flattened elements.
|
761
|
+
|
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
|
771
|
+
|
772
|
+
|
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.
|
798
|
+
|
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.
|
804
|
+
|
805
|
+
Returns:
|
806
|
+
Any: The result of the function call.
|
807
|
+
|
808
|
+
Raises:
|
809
|
+
Exception: Propagates any exceptions not handled by the error_map.
|
810
|
+
|
811
|
+
Examples:
|
812
|
+
>>> async def async_add(x, y): return x + y
|
813
|
+
>>> asyncio.run(call_handler(async_add, 1, 2))
|
814
|
+
3
|
815
|
+
"""
|
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)
|
835
|
+
|
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
|
842
|
+
|
843
|
+
|
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.
|
849
|
+
|
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.
|
799
855
|
|
856
|
+
Returns:
|
857
|
+
List[Any]: A list of results after asynchronously applying the function.
|
800
858
|
|
859
|
+
Examples:
|
860
|
+
>>> async def square(x): return x * x
|
861
|
+
>>> asyncio.run(alcall([1, 2, 3], square))
|
862
|
+
[1, 4, 9]
|
863
|
+
"""
|
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)
|
801
868
|
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
#
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
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:
|
875
|
+
"""
|
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.
|
888
|
+
|
889
|
+
Returns:
|
890
|
+
Any: The result of the function call, or (result, duration) if timing is True.
|
891
|
+
|
892
|
+
Examples:
|
893
|
+
>>> async def example_func(x): return x
|
894
|
+
>>> asyncio.run(tcall(example_func, 5, timing=True))
|
895
|
+
(5, duration)
|
896
|
+
"""
|
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
|
+
|