lionagi 0.0.306__py3-none-any.whl → 0.0.307__py3-none-any.whl

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