sunholo 0.101.2__py3-none-any.whl → 0.101.5__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.
- sunholo/invoke/async_class.py +34 -41
- {sunholo-0.101.2.dist-info → sunholo-0.101.5.dist-info}/METADATA +2 -2
- {sunholo-0.101.2.dist-info → sunholo-0.101.5.dist-info}/RECORD +7 -7
- {sunholo-0.101.2.dist-info → sunholo-0.101.5.dist-info}/LICENSE.txt +0 -0
- {sunholo-0.101.2.dist-info → sunholo-0.101.5.dist-info}/WHEEL +0 -0
- {sunholo-0.101.2.dist-info → sunholo-0.101.5.dist-info}/entry_points.txt +0 -0
- {sunholo-0.101.2.dist-info → sunholo-0.101.5.dist-info}/top_level.txt +0 -0
sunholo/invoke/async_class.py
CHANGED
|
@@ -8,17 +8,23 @@ from tenacity import AsyncRetrying, retry_if_exception_type, wait_random_exponen
|
|
|
8
8
|
log = setup_logging("sunholo_AsyncTaskRunner")
|
|
9
9
|
|
|
10
10
|
class AsyncTaskRunner:
|
|
11
|
-
def __init__(self, retry_enabled=False, retry_kwargs=None):
|
|
11
|
+
def __init__(self, retry_enabled=False, retry_kwargs=None, timeout=120):
|
|
12
12
|
self.tasks = []
|
|
13
13
|
self.retry_enabled = retry_enabled
|
|
14
14
|
self.retry_kwargs = retry_kwargs or {}
|
|
15
|
+
self.timeout = timeout
|
|
15
16
|
|
|
16
|
-
def add_task(self, func: Callable[..., Any], *args: Any):
|
|
17
|
+
def add_task(self, func: Callable[..., Any], *args: Any, **kwargs: Any):
|
|
17
18
|
"""
|
|
18
|
-
Adds a task to the list of tasks to be executed.
|
|
19
|
+
Adds a task to the list of tasks to be executed, supporting both positional and keyword arguments.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
func: The function to be executed.
|
|
23
|
+
*args: Positional arguments for the function.
|
|
24
|
+
**kwargs: Keyword arguments for the function.
|
|
19
25
|
"""
|
|
20
|
-
log.info(f"Adding task: {func.__name__} with args: {args}")
|
|
21
|
-
self.tasks.append((func.__name__, func, args))
|
|
26
|
+
log.info(f"Adding task: {func.__name__} with args: {args}, kwargs: {kwargs}")
|
|
27
|
+
self.tasks.append((func.__name__, func, args, kwargs))
|
|
22
28
|
|
|
23
29
|
async def run_async_as_completed(self) -> AsyncGenerator[Dict[str, Any], None]:
|
|
24
30
|
"""
|
|
@@ -26,53 +32,36 @@ class AsyncTaskRunner:
|
|
|
26
32
|
while periodically sending heartbeat messages.
|
|
27
33
|
"""
|
|
28
34
|
log.info("Running tasks asynchronously and yielding results as they complete")
|
|
29
|
-
|
|
30
|
-
# Create a queue for inter-coroutine communication
|
|
31
35
|
queue = asyncio.Queue()
|
|
32
|
-
|
|
33
|
-
# List to keep track of all running tasks and their heartbeats
|
|
34
36
|
task_infos = []
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# Create an event to signal task completion to the heartbeat
|
|
38
|
+
for name, func, args, kwargs in self.tasks:
|
|
39
|
+
log.info(f"Executing task: {name=}, {func=} with args: {args}, kwargs: {kwargs}")
|
|
39
40
|
completion_event = asyncio.Event()
|
|
40
|
-
|
|
41
|
-
# Start the main task with retries
|
|
42
|
-
task_coro = self._run_with_retries(name, func, *args, queue=queue, completion_event=completion_event)
|
|
41
|
+
task_coro = self._run_with_retries_and_timeout(name, func, args, kwargs, queue, completion_event)
|
|
43
42
|
task = asyncio.create_task(task_coro)
|
|
44
|
-
|
|
45
|
-
# Start the heartbeat coroutine
|
|
46
43
|
heartbeat_coro = self._send_heartbeat(name, completion_event, queue)
|
|
47
44
|
heartbeat_task = asyncio.create_task(heartbeat_coro)
|
|
48
|
-
|
|
49
|
-
# Store task information for management
|
|
50
45
|
task_infos.append({
|
|
51
46
|
'name': name,
|
|
52
47
|
'task': task,
|
|
53
48
|
'heartbeat_task': heartbeat_task,
|
|
54
49
|
'completion_event': completion_event
|
|
55
50
|
})
|
|
56
|
-
|
|
57
51
|
log.info(f"Started task '{name}' and its heartbeat")
|
|
58
52
|
|
|
59
53
|
log.info(f"Started async run with {len(self.tasks)} tasks and heartbeats")
|
|
60
|
-
|
|
61
|
-
# Create a monitor task to detect when all tasks and heartbeats are done
|
|
62
54
|
monitor = asyncio.create_task(self._monitor_tasks(task_infos, queue))
|
|
63
55
|
|
|
64
|
-
# Continuously yield messages from the queue until sentinel is received
|
|
65
56
|
while True:
|
|
66
57
|
message = await queue.get()
|
|
67
58
|
if message is None:
|
|
68
59
|
log.info("Received sentinel. Exiting message loop.")
|
|
69
|
-
break
|
|
60
|
+
break
|
|
70
61
|
log.info(f"Received message from queue: {message}")
|
|
71
62
|
yield message
|
|
72
63
|
|
|
73
|
-
# Wait for the monitor to finish
|
|
74
64
|
await monitor
|
|
75
|
-
|
|
76
65
|
log.info("All tasks and heartbeats have completed")
|
|
77
66
|
|
|
78
67
|
async def _monitor_tasks(self, task_infos, queue):
|
|
@@ -94,15 +83,18 @@ class AsyncTaskRunner:
|
|
|
94
83
|
pass
|
|
95
84
|
log.info(f"Monitor: Heartbeat for task '{info['name']}' has been canceled")
|
|
96
85
|
|
|
97
|
-
# Send a sentinel to indicate completion
|
|
98
86
|
await queue.put(None)
|
|
99
87
|
log.info("Monitor: Sent sentinel to queue")
|
|
100
88
|
|
|
101
|
-
async def
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
89
|
+
async def _run_with_retries_and_timeout(self,
|
|
90
|
+
name: str,
|
|
91
|
+
func: Callable[..., Any],
|
|
92
|
+
args: tuple,
|
|
93
|
+
kwargs: dict,
|
|
94
|
+
queue: asyncio.Queue,
|
|
95
|
+
completion_event: asyncio.Event) -> None:
|
|
105
96
|
try:
|
|
97
|
+
log.info(f"run_with_retries_and_timeout: {name=}, {func=} with args: {args}, kwargs: {kwargs}")
|
|
106
98
|
if self.retry_enabled:
|
|
107
99
|
retry_kwargs = {
|
|
108
100
|
'wait': wait_random_exponential(multiplier=1, max=60),
|
|
@@ -113,39 +105,41 @@ class AsyncTaskRunner:
|
|
|
113
105
|
async for attempt in AsyncRetrying(**retry_kwargs):
|
|
114
106
|
with attempt:
|
|
115
107
|
log.info(f"Starting task '{name}' with retry")
|
|
116
|
-
result = await self._execute_task(func, *args)
|
|
108
|
+
result = await asyncio.wait_for(self._execute_task(func, *args, **kwargs), timeout=self.timeout)
|
|
117
109
|
await queue.put({'type': 'task_complete', 'func_name': name, 'result': result})
|
|
118
110
|
log.info(f"Sent 'task_complete' message for task '{name}'")
|
|
119
111
|
return
|
|
120
112
|
else:
|
|
121
113
|
log.info(f"Starting task '{name}' with no retry")
|
|
122
|
-
result = await self._execute_task(func, *args)
|
|
114
|
+
result = await asyncio.wait_for(self._execute_task(func, *args, **kwargs), timeout=self.timeout)
|
|
123
115
|
await queue.put({'type': 'task_complete', 'func_name': name, 'result': result})
|
|
124
116
|
log.info(f"Sent 'task_complete' message for task '{name}'")
|
|
117
|
+
except asyncio.TimeoutError:
|
|
118
|
+
log.error(f"Task '{name}' timed out after {self.timeout} seconds")
|
|
119
|
+
await queue.put({'type': 'task_error', 'func_name': name, 'error': f'Task timed out after {self.timeout} seconds'})
|
|
125
120
|
except Exception as e:
|
|
126
121
|
log.error(f"Error in task '{name}': {e}\n{traceback.format_exc()}")
|
|
127
122
|
await queue.put({'type': 'task_error', 'func_name': name, 'error': f'{e}\n{traceback.format_exc()}'})
|
|
128
|
-
log.info(f"Sent 'task_error' message for task '{name}'")
|
|
129
123
|
finally:
|
|
130
124
|
log.info(f"Task '{name}' completed.")
|
|
131
|
-
# Set the completion event after sending the message
|
|
132
125
|
completion_event.set()
|
|
133
126
|
|
|
134
|
-
async def _execute_task(self, func: Callable[..., Any], *args: Any) -> Any:
|
|
127
|
+
async def _execute_task(self, func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
|
|
135
128
|
"""
|
|
136
129
|
Executes the given task function and returns its result.
|
|
137
130
|
|
|
138
131
|
Args:
|
|
139
132
|
func (Callable): The callable to execute.
|
|
140
|
-
*args:
|
|
133
|
+
*args: Positional arguments to pass to the callable.
|
|
134
|
+
**kwargs: Keyword arguments to pass to the callable.
|
|
141
135
|
|
|
142
136
|
Returns:
|
|
143
137
|
Any: The result of the task.
|
|
144
138
|
"""
|
|
145
139
|
if asyncio.iscoroutinefunction(func):
|
|
146
|
-
return await func(*args)
|
|
140
|
+
return await func(*args, **kwargs)
|
|
147
141
|
else:
|
|
148
|
-
return await asyncio.to_thread(func, *args)
|
|
142
|
+
return await asyncio.to_thread(func, *args, **kwargs)
|
|
149
143
|
|
|
150
144
|
async def _send_heartbeat(self, func_name: str, completion_event: asyncio.Event, queue: asyncio.Queue, interval: int = 2):
|
|
151
145
|
"""
|
|
@@ -174,5 +168,4 @@ class AsyncTaskRunner:
|
|
|
174
168
|
except asyncio.CancelledError:
|
|
175
169
|
log.info(f"Heartbeat for task '{func_name}' has been canceled")
|
|
176
170
|
finally:
|
|
177
|
-
log.info(f"Heartbeat for task '{func_name}' stopped")
|
|
178
|
-
|
|
171
|
+
log.info(f"Heartbeat for task '{func_name}' stopped")
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sunholo
|
|
3
|
-
Version: 0.101.
|
|
3
|
+
Version: 0.101.5
|
|
4
4
|
Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
|
|
5
5
|
Home-page: https://github.com/sunholo-data/sunholo-py
|
|
6
|
-
Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.101.
|
|
6
|
+
Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.101.5.tar.gz
|
|
7
7
|
Author: Holosun ApS
|
|
8
8
|
Author-email: multivac@sunholo.com
|
|
9
9
|
License: Apache License, Version 2.0
|
|
@@ -91,7 +91,7 @@ sunholo/genai/init.py,sha256=yG8E67TduFCTQPELo83OJuWfjwTnGZsyACospahyEaY,687
|
|
|
91
91
|
sunholo/genai/process_funcs_cls.py,sha256=krjhwruW-7OUR0gEOPC5Ew9KOoAoTWDMW5HfvpvuPNM,28755
|
|
92
92
|
sunholo/genai/safety.py,sha256=mkFDO_BeEgiKjQd9o2I4UxB6XI7a9U-oOFjZ8LGRUC4,1238
|
|
93
93
|
sunholo/invoke/__init__.py,sha256=o1RhwBGOtVK0MIdD55fAIMCkJsxTksi8GD5uoqVKI-8,184
|
|
94
|
-
sunholo/invoke/async_class.py,sha256=
|
|
94
|
+
sunholo/invoke/async_class.py,sha256=G8vD2H94fpBc37mSJSQODEKJ67P2mPQEHabtDaLOvxE,8033
|
|
95
95
|
sunholo/invoke/direct_vac_func.py,sha256=GXSCMkC6vOWGUtQjxy-ZpTrMvJa3CgcW-y9mDpJwWC8,9533
|
|
96
96
|
sunholo/invoke/invoke_vac_utils.py,sha256=sJc1edHTHMzMGXjji1N67c3iUaP7BmAL5nj82Qof63M,2053
|
|
97
97
|
sunholo/langfuse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -147,9 +147,9 @@ sunholo/vertex/init.py,sha256=1OQwcPBKZYBTDPdyU7IM4X4OmiXLdsNV30C-fee2scQ,2875
|
|
|
147
147
|
sunholo/vertex/memory_tools.py,sha256=tBZxqVZ4InTmdBvLlOYwoSEWu4-kGquc-gxDwZCC4FA,7667
|
|
148
148
|
sunholo/vertex/safety.py,sha256=S9PgQT1O_BQAkcqauWncRJaydiP8Q_Jzmu9gxYfy1VA,2482
|
|
149
149
|
sunholo/vertex/type_dict_to_json.py,sha256=uTzL4o9tJRao4u-gJOFcACgWGkBOtqACmb6ihvCErL8,4694
|
|
150
|
-
sunholo-0.101.
|
|
151
|
-
sunholo-0.101.
|
|
152
|
-
sunholo-0.101.
|
|
153
|
-
sunholo-0.101.
|
|
154
|
-
sunholo-0.101.
|
|
155
|
-
sunholo-0.101.
|
|
150
|
+
sunholo-0.101.5.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
|
|
151
|
+
sunholo-0.101.5.dist-info/METADATA,sha256=29r2NF8uW_rC_tcj2FqzKRZi55czwnq3kGheIdpNw2c,8312
|
|
152
|
+
sunholo-0.101.5.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
153
|
+
sunholo-0.101.5.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
|
|
154
|
+
sunholo-0.101.5.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
|
|
155
|
+
sunholo-0.101.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|