sunholo 0.100.1__py3-none-any.whl → 0.100.2__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/auth/refresh.py CHANGED
@@ -14,7 +14,7 @@ def get_default_email():
14
14
 
15
15
  if gcs_credentials is None:
16
16
  log.error("Could not refresh the credentials properly.")
17
- return None
17
+ return None, None
18
18
 
19
19
  service_account_email = getattr(gcs_credentials, 'service_account_email', None)
20
20
  # If you use a service account credential, you can use the embedded email
@@ -23,7 +23,7 @@ def get_default_email():
23
23
  if not service_account_email:
24
24
  log.error("Could not create the credentials for signed requests - no credentials.service_account_email or GCS_MAIL_USER with roles/iam.serviceAccountTokenCreator")
25
25
 
26
- return None
26
+ return None, None
27
27
 
28
28
  log.info(f"Found default email: {service_account_email=} for {project_id=}")
29
29
  return service_account_email, gcs_credentials
sunholo/gcs/add_file.py CHANGED
@@ -182,8 +182,6 @@ def add_file_to_gcs(filename: str,
182
182
 
183
183
  blob.metadata = the_metadata
184
184
 
185
-
186
-
187
185
  max_retries = 5
188
186
  base_delay = 1 # 1 second
189
187
  for attempt in range(max_retries):
@@ -106,7 +106,14 @@ def sign_gcs_url(bucket_name:str, object_name:str, expiry_secs:int = 86400) -> O
106
106
  Returns:
107
107
  str: The signed URL or None if not avialable
108
108
  """
109
- service_account_email, gcs_credentials = get_default_email()
109
+ result = get_default_email()
110
+
111
+ # Check if the result is None
112
+ if result is None or any(item is None for item in result):
113
+ log.error("Failed to retrieve the service account email and credentials.")
114
+ return None
115
+
116
+ service_account_email, gcs_credentials = result
110
117
 
111
118
  expires = datetime.now() + timedelta(seconds=expiry_secs)
112
119
 
@@ -262,8 +262,7 @@ class GenAIFunctionProcessor:
262
262
  # Execute the function with the provided parameters
263
263
  result = fn_exec(**params_obj)
264
264
  log.info(f"Got result from {function_name}: {result} of type: {type(result)}")
265
- if not isinstance(result, str):
266
- log.warning(f"Tool functions should return strings: {function_name} returned type: {type(result)}")
265
+ #TODO: return images
267
266
  else:
268
267
  fn_result = type(fn).to_dict(fn)
269
268
  result = fn_result.get("result")
@@ -9,68 +9,54 @@ class AsyncTaskRunner:
9
9
  self.tasks = []
10
10
  self.retry_enabled = retry_enabled
11
11
  self.retry_kwargs = retry_kwargs or {}
12
-
12
+
13
13
  def add_task(self, func: Callable[..., Any], *args: Any):
14
14
  """Adds a task to the list of tasks to be executed."""
15
15
  log.info(f"Adding task: {func.__name__} with args: {args}")
16
16
  self.tasks.append((func.__name__, func, args))
17
-
18
- async def run_async_as_completed(self) -> AsyncGenerator[Dict[str, Any], None]:
17
+
18
+ async def run_async_as_completed(self, callback=None) -> AsyncGenerator[Dict[str, Any], None]:
19
19
  """
20
- Runs all tasks concurrently and yields results and heartbeats as they are produced.
20
+ Runs all tasks concurrently and yields results as they complete, while periodically sending heartbeat messages.
21
+
22
+ Args:
23
+ callback: The callback object that will receive heartbeat messages.
21
24
  """
22
- log.info("Running tasks asynchronously and yielding results and heartbeats as they occur")
23
- queue = asyncio.Queue()
25
+ log.info("Running tasks asynchronously and yielding results as they complete")
24
26
  tasks = {}
25
- completed_tasks = set()
26
-
27
27
  for name, func, args in self.tasks:
28
- coro = self._task_wrapper(name, func, args, queue)
28
+ # Pass the callback down to _task_wrapper
29
+ coro = self._task_wrapper(name, func, args, callback)
29
30
  task = asyncio.create_task(coro)
30
31
  tasks[task] = name
32
+
33
+ log.info(f"Start async run with {len(self.tasks)} runners")
34
+ while tasks:
35
+ done, _ = await asyncio.wait(tasks.keys(), return_when=asyncio.FIRST_COMPLETED)
36
+ for task in done:
37
+ name = tasks.pop(task)
38
+ try:
39
+ # func_name = message['func_name']; result = message['result']
40
+ result = await task
41
+ yield {'type':'task_complete', 'func_name': name, 'result': result}
42
+ except Exception as e:
43
+ log.error(f"Task {name} resulted in an error: {e}\n{traceback.format_exc()}")
44
+ yield {'type':'task_error', 'func_name': name, 'error': f'{e}\n{traceback.format_exc()}'}
31
45
 
32
- while tasks or not queue.empty():
33
- if not queue.empty():
34
- message = await queue.get()
35
- log.info(f"Found queue message: {message}")
36
- # Ignore heartbeats from completed tasks
37
- if message['type'] == 'heartbeat' and message['func_name'] in completed_tasks:
38
- continue
39
- yield message
40
- else:
41
- done, _ = await asyncio.wait(
42
- list(tasks.keys()),
43
- timeout=0.1,
44
- return_when=asyncio.FIRST_COMPLETED
45
- )
46
- for task in done:
47
- name = tasks.pop(task)
48
- completed_tasks.add(name)
49
- try:
50
- result = await task
51
- await queue.put({'type': 'task_complete', 'func_name': name, 'result': result})
52
- except Exception as e:
53
- log.error(f"Task {name} resulted in an error: {e}\n{traceback.format_exc()}")
54
- await queue.put({'type': 'task_error', 'func_name': name, 'error': e})
55
-
56
- # Process any remaining messages in the queue
57
- while not queue.empty():
58
- message = await queue.get()
59
- log.info(f"Found queue message: {message}")
60
- if message['type'] == 'heartbeat' and message['func_name'] in completed_tasks:
61
- continue
62
- yield message
63
-
64
- async def _task_wrapper(self, name: str, func: Callable[..., Any], args: Any, queue: asyncio.Queue) -> Any:
46
+ async def _task_wrapper(self, name: str, func: Callable[..., Any], args: Any, callback=None) -> Any:
65
47
  """Wraps the task function to process its output and handle retries, while managing heartbeat updates."""
66
48
  async def run_func():
67
49
  if asyncio.iscoroutinefunction(func):
50
+ # If the function is async, await it
68
51
  return await func(*args)
69
52
  else:
53
+ # If the function is sync, run it in a thread to prevent blocking
70
54
  return await asyncio.to_thread(func, *args)
71
55
 
72
- # Start the heartbeat task
73
- heartbeat_task = asyncio.create_task(self._send_heartbeat(queue, name))
56
+ # Start the heartbeat task if a callback is provided
57
+ heartbeat_task = None
58
+ if callback:
59
+ heartbeat_task = asyncio.create_task(self._send_heartbeat(callback, name))
74
60
 
75
61
  try:
76
62
  if self.retry_enabled:
@@ -91,48 +77,41 @@ class AsyncTaskRunner:
91
77
  raise
92
78
  finally:
93
79
  # Stop the heartbeat task
94
- heartbeat_task.cancel()
95
- # Wait for the heartbeat task to finish
96
- try:
97
- await heartbeat_task
98
- except asyncio.CancelledError:
99
- pass
80
+ if heartbeat_task:
81
+ heartbeat_task.cancel()
82
+ try:
83
+ await heartbeat_task # Ensure the heartbeat task is properly canceled
84
+ except asyncio.CancelledError:
85
+ pass
86
+
87
+ # Send a message indicating task completion and remove spinner
88
+ if callback:
89
+ completion_html = (
90
+ f"<script>"
91
+ f"document.getElementById('{name}-spinner').innerHTML = '✔️ Task {name} completed!';"
92
+ f"</script>"
93
+ )
94
+ await callback.async_on_llm_new_token(token=completion_html)
100
95
 
101
- async def _send_heartbeat(self, queue: asyncio.Queue, func_name: str, interval=2):
96
+ async def _send_heartbeat(self, callback, func_name, interval=2):
102
97
  """
103
- Sends a periodic heartbeat to keep the task alive and update the spinner with elapsed time.
98
+ Sends periodic heartbeat updates to indicate the task is still in progress.
99
+
100
+ Args:
101
+ callback: The callback to notify that the task is still working.
102
+ func_name: The name of the task function.
103
+ interval: How frequently to send heartbeat messages (in seconds).
104
104
  """
105
105
  # Send the initial spinner HTML
106
106
  spinner_html = (
107
- f'<div id="{func_name}-spinner" class="spinner-container">'
108
- f' <div class="spinner"></div>'
109
- f' <span class="elapsed-time">Task {func_name} is still running... 0s elapsed</span>'
110
- f'</div>'
107
+ f'<div id="{func_name}-spinner" style="display:inline-block; margin: 5px;">'
108
+ f'<div class="spinner" style="width:16px; height:16px; border: 2px solid #f3f3f3; '
109
+ f'border-radius: 50%; border-top: 2px solid #3498db; animation: spin 1s linear infinite;"></div>'
110
+ f'<style>@keyframes spin {{0% {{ transform: rotate(0deg); }} 100% {{ transform: rotate(360deg); }}}}</style>'
111
+ f' <span>Task {func_name} is in progress...</span></div>'
111
112
  )
112
- log.info(f"Heartbeat started for task {func_name}")
113
-
114
- await queue.put({'type': 'heartbeat', 'func_name': func_name, 'token': spinner_html})
113
+ await callback.async_on_llm_new_token(token=spinner_html)
115
114
 
116
- # Keep track of elapsed time
117
- elapsed_time = 0
118
-
119
- try:
120
- while True:
121
- await asyncio.sleep(interval) # Sleep for the interval
122
- elapsed_time += interval # Increment elapsed time
123
- log.info(f"Sending heartbeat for {func_name}: {elapsed_time}s elapsed")
124
- # Update spinner with the elapsed time
125
- update_html = (
126
- f'<div style="display: none;" data-update-id="{func_name}-spinner">'
127
- f'<span class="elapsed-time">Task {func_name} is still running... {elapsed_time}s elapsed</span>'
128
- f'</div>'
129
- )
130
- await queue.put({'type': 'heartbeat', 'func_name': func_name, 'token': update_html})
131
- except asyncio.CancelledError:
132
- log.info(f"Heartbeat task for {func_name} has been cancelled.")
133
- finally:
134
- # Send a message indicating task completion to update the spinner's state
135
- completion_html = (
136
- f'<div style="display: none;" data-complete-id="{func_name}-spinner"></div>'
137
- )
138
- await queue.put({'type': 'heartbeat', 'func_name': func_name, 'token': completion_html})
115
+ # Keep sending heartbeats until task completes
116
+ while True:
117
+ await asyncio.sleep(interval) # Sleep for the interval but do not send multiple messages
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.100.1
3
+ Version: 0.100.2
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.100.1.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.100.2.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
@@ -19,7 +19,7 @@ sunholo/archive/__init__.py,sha256=qNHWm5rGPVOlxZBZCpA1wTYPbalizRT7f8X4rs2t290,3
19
19
  sunholo/archive/archive.py,sha256=PxVfDtO2_2ZEEbnhXSCbXLdeoHoQVImo4y3Jr2XkCFY,1204
20
20
  sunholo/auth/__init__.py,sha256=TeP-OY0XGxYV_8AQcVGoh35bvyWhNUcMRfhuD5l44Sk,91
21
21
  sunholo/auth/gcloud.py,sha256=PdbwkuTdRi4RKBmgG9uwsReegqC4VG15_tw5uzmA7Fs,298
22
- sunholo/auth/refresh.py,sha256=6AEWX87G3I9BCqrgGJjHGrrWABBXHuaGDKU9ZEcVeXM,2017
22
+ sunholo/auth/refresh.py,sha256=WSlKa8TQ70GlZ2e0u83nYknhUsgTeiyyTVi-EFOa8Uc,2029
23
23
  sunholo/auth/run.py,sha256=pMSp2lzL6e6ZqlltVUH92bkeUt341yMue027qrE0jQU,2821
24
24
  sunholo/azure/__init__.py,sha256=S1WQ5jndzNgzhSBh9UpX_yw7hRVm3hCzkAWNxUdK4dA,48
25
25
  sunholo/azure/auth.py,sha256=Y3fDqFLYwbsIyi5hS5L-3hYnwrLWVL96yPng5Sj5c2c,2236
@@ -80,16 +80,16 @@ sunholo/embedder/embed_chunk.py,sha256=_FdO4-9frUJlDPqXv2Ai49ejUrrMTMGti3D7bfJGf
80
80
  sunholo/excel/__init__.py,sha256=AqTMN9K4qJYi4maEgoORc5oxDVGO_eqmwzDaVP37JgY,56
81
81
  sunholo/excel/plugin.py,sha256=rl3FoECZ6Ts8KKExPrbPwr3u3CegZfsevmcjgUXAlhE,4033
82
82
  sunholo/gcs/__init__.py,sha256=SZvbsMFDko40sIRHTHppA37IijvJTae54vrhooEF5-4,90
83
- sunholo/gcs/add_file.py,sha256=XuntzdFz9b0flMArgUe-4DSaPI7zdBw8hlsG1rsRQmc,8371
83
+ sunholo/gcs/add_file.py,sha256=NxUaWTP-h9l5fbjnrEYpipo5rjS3uPkjMWeDhpK113g,8369
84
84
  sunholo/gcs/download_folder.py,sha256=ijJTnS595JqZhBH8iHFErQilMbkuKgL-bnTCMLGuvlA,1614
85
- sunholo/gcs/download_url.py,sha256=q1NiJSvEhdNrmU5ZJ-sBCMC_J5CxzrajY8LRgdPOV_M,6130
85
+ sunholo/gcs/download_url.py,sha256=oPC8phesRcpXB4zB_p0nWPKPBMs7JBhhVJ7tnVYhzD8,6356
86
86
  sunholo/gcs/metadata.py,sha256=oQLcXi4brsZ74aegWyC1JZmhlaEV270HS5_UWtAYYWE,898
87
87
  sunholo/genai/__init__.py,sha256=dBl6IA3-Fx6-Vx81r0XqxHlUq6WeW1iDX188dpChu8s,115
88
88
  sunholo/genai/init.py,sha256=yG8E67TduFCTQPELo83OJuWfjwTnGZsyACospahyEaY,687
89
- sunholo/genai/process_funcs_cls.py,sha256=MF3wm-N-zoYvme4I8ffXM9I7cog8OFyBnLu1e3A6eVc,26695
89
+ sunholo/genai/process_funcs_cls.py,sha256=DPe70E71pofInLMFBFdudFZr0ZCFHN1LFCQjpxtG8xU,26552
90
90
  sunholo/genai/safety.py,sha256=mkFDO_BeEgiKjQd9o2I4UxB6XI7a9U-oOFjZ8LGRUC4,1238
91
91
  sunholo/invoke/__init__.py,sha256=o1RhwBGOtVK0MIdD55fAIMCkJsxTksi8GD5uoqVKI-8,184
92
- sunholo/invoke/async_class.py,sha256=TbYzdS2RDu1WhbYcq6wt7GTJzrtk-7Y3I-2AbJAD1Ik,6134
92
+ sunholo/invoke/async_class.py,sha256=LNe57uxwGwLaN44MCOVOOSMNX8FZ5buqCxevYTHUTPg,5501
93
93
  sunholo/invoke/direct_vac_func.py,sha256=GXSCMkC6vOWGUtQjxy-ZpTrMvJa3CgcW-y9mDpJwWC8,9533
94
94
  sunholo/invoke/invoke_vac_utils.py,sha256=sJc1edHTHMzMGXjji1N67c3iUaP7BmAL5nj82Qof63M,2053
95
95
  sunholo/langfuse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -144,9 +144,9 @@ sunholo/vertex/init.py,sha256=1OQwcPBKZYBTDPdyU7IM4X4OmiXLdsNV30C-fee2scQ,2875
144
144
  sunholo/vertex/memory_tools.py,sha256=tBZxqVZ4InTmdBvLlOYwoSEWu4-kGquc-gxDwZCC4FA,7667
145
145
  sunholo/vertex/safety.py,sha256=S9PgQT1O_BQAkcqauWncRJaydiP8Q_Jzmu9gxYfy1VA,2482
146
146
  sunholo/vertex/type_dict_to_json.py,sha256=uTzL4o9tJRao4u-gJOFcACgWGkBOtqACmb6ihvCErL8,4694
147
- sunholo-0.100.1.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
148
- sunholo-0.100.1.dist-info/METADATA,sha256=dFU8BBZlnOGZpfnkvOiQ-_yO3Hs12mNi0BFWnVQh9QQ,8312
149
- sunholo-0.100.1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
150
- sunholo-0.100.1.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
151
- sunholo-0.100.1.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
152
- sunholo-0.100.1.dist-info/RECORD,,
147
+ sunholo-0.100.2.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
148
+ sunholo-0.100.2.dist-info/METADATA,sha256=hkHwKSigiDOe9t6fcubbDM0Osr542-T3R8guKamW2YY,8312
149
+ sunholo-0.100.2.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
150
+ sunholo-0.100.2.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
151
+ sunholo-0.100.2.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
152
+ sunholo-0.100.2.dist-info/RECORD,,