lionagi 0.0.201__py3-none-any.whl → 0.0.204__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lionagi/_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
|
+
|