sunholo 0.100.1__tar.gz → 0.100.3__tar.gz

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.
Files changed (161) hide show
  1. {sunholo-0.100.1 → sunholo-0.100.3}/PKG-INFO +2 -2
  2. {sunholo-0.100.1 → sunholo-0.100.3}/setup.py +1 -1
  3. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/auth/refresh.py +2 -2
  4. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/gcs/add_file.py +0 -2
  5. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/gcs/download_url.py +8 -1
  6. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/genai/process_funcs_cls.py +1 -2
  7. sunholo-0.100.3/sunholo/invoke/async_class.py +178 -0
  8. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo.egg-info/PKG-INFO +2 -2
  9. sunholo-0.100.1/sunholo/invoke/async_class.py +0 -138
  10. {sunholo-0.100.1 → sunholo-0.100.3}/LICENSE.txt +0 -0
  11. {sunholo-0.100.1 → sunholo-0.100.3}/MANIFEST.in +0 -0
  12. {sunholo-0.100.1 → sunholo-0.100.3}/README.md +0 -0
  13. {sunholo-0.100.1 → sunholo-0.100.3}/setup.cfg +0 -0
  14. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/__init__.py +0 -0
  15. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/agents/__init__.py +0 -0
  16. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/agents/chat_history.py +0 -0
  17. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/agents/dispatch_to_qa.py +0 -0
  18. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/agents/fastapi/__init__.py +0 -0
  19. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/agents/fastapi/base.py +0 -0
  20. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/agents/fastapi/qna_routes.py +0 -0
  21. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/agents/flask/__init__.py +0 -0
  22. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/agents/flask/base.py +0 -0
  23. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/agents/flask/qna_routes.py +0 -0
  24. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/agents/flask/vac_routes.py +0 -0
  25. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/agents/langserve.py +0 -0
  26. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/agents/pubsub.py +0 -0
  27. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/agents/route.py +0 -0
  28. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/agents/special_commands.py +0 -0
  29. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/agents/swagger.py +0 -0
  30. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/archive/__init__.py +0 -0
  31. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/archive/archive.py +0 -0
  32. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/auth/__init__.py +0 -0
  33. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/auth/gcloud.py +0 -0
  34. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/auth/run.py +0 -0
  35. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/azure/__init__.py +0 -0
  36. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/azure/auth.py +0 -0
  37. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/azure/blobs.py +0 -0
  38. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/azure/event_grid.py +0 -0
  39. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/bots/__init__.py +0 -0
  40. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/bots/discord.py +0 -0
  41. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/bots/github_webhook.py +0 -0
  42. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/bots/webapp.py +0 -0
  43. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/chunker/__init__.py +0 -0
  44. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/chunker/azure.py +0 -0
  45. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/chunker/doc_handling.py +0 -0
  46. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/chunker/encode_metadata.py +0 -0
  47. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/chunker/images.py +0 -0
  48. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/chunker/loaders.py +0 -0
  49. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/chunker/message_data.py +0 -0
  50. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/chunker/pdfs.py +0 -0
  51. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/chunker/process_chunker_data.py +0 -0
  52. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/chunker/publish.py +0 -0
  53. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/chunker/pubsub.py +0 -0
  54. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/chunker/splitter.py +0 -0
  55. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/cli/__init__.py +0 -0
  56. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/cli/chat_vac.py +0 -0
  57. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/cli/cli.py +0 -0
  58. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/cli/cli_init.py +0 -0
  59. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/cli/configs.py +0 -0
  60. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/cli/deploy.py +0 -0
  61. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/cli/embedder.py +0 -0
  62. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/cli/merge_texts.py +0 -0
  63. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/cli/run_proxy.py +0 -0
  64. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/cli/sun_rich.py +0 -0
  65. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/cli/swagger.py +0 -0
  66. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/cli/vertex.py +0 -0
  67. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/components/__init__.py +0 -0
  68. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/components/llm.py +0 -0
  69. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/components/retriever.py +0 -0
  70. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/components/vectorstore.py +0 -0
  71. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/custom_logging.py +0 -0
  72. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/database/__init__.py +0 -0
  73. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/database/alloydb.py +0 -0
  74. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/database/alloydb_client.py +0 -0
  75. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/database/database.py +0 -0
  76. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/database/lancedb.py +0 -0
  77. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/database/sql/sb/create_function.sql +0 -0
  78. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/database/sql/sb/create_function_time.sql +0 -0
  79. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/database/sql/sb/create_table.sql +0 -0
  80. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/database/sql/sb/delete_source_row.sql +0 -0
  81. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/database/sql/sb/return_sources.sql +0 -0
  82. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/database/sql/sb/setup.sql +0 -0
  83. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/database/static_dbs.py +0 -0
  84. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/database/uuid.py +0 -0
  85. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/discovery_engine/__init__.py +0 -0
  86. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/discovery_engine/chunker_handler.py +0 -0
  87. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/discovery_engine/create_new.py +0 -0
  88. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/discovery_engine/discovery_engine_client.py +0 -0
  89. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/discovery_engine/get_ai_search_chunks.py +0 -0
  90. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/embedder/__init__.py +0 -0
  91. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/embedder/embed_chunk.py +0 -0
  92. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/excel/__init__.py +0 -0
  93. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/excel/plugin.py +0 -0
  94. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/gcs/__init__.py +0 -0
  95. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/gcs/download_folder.py +0 -0
  96. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/gcs/metadata.py +0 -0
  97. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/genai/__init__.py +0 -0
  98. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/genai/init.py +0 -0
  99. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/genai/safety.py +0 -0
  100. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/invoke/__init__.py +0 -0
  101. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/invoke/direct_vac_func.py +0 -0
  102. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/invoke/invoke_vac_utils.py +0 -0
  103. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/langfuse/__init__.py +0 -0
  104. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/langfuse/callback.py +0 -0
  105. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/langfuse/evals.py +0 -0
  106. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/langfuse/prompts.py +0 -0
  107. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/llamaindex/__init__.py +0 -0
  108. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/llamaindex/get_files.py +0 -0
  109. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/llamaindex/import_files.py +0 -0
  110. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/llamaindex/llamaindex_class.py +0 -0
  111. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/llamaindex/user_history.py +0 -0
  112. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/lookup/__init__.py +0 -0
  113. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/lookup/model_lookup.yaml +0 -0
  114. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/patches/__init__.py +0 -0
  115. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/patches/langchain/__init__.py +0 -0
  116. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/patches/langchain/lancedb.py +0 -0
  117. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/patches/langchain/vertexai.py +0 -0
  118. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/pubsub/__init__.py +0 -0
  119. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/pubsub/process_pubsub.py +0 -0
  120. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/pubsub/pubsub_manager.py +0 -0
  121. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/qna/__init__.py +0 -0
  122. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/qna/parsers.py +0 -0
  123. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/qna/retry.py +0 -0
  124. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/streaming/__init__.py +0 -0
  125. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/streaming/content_buffer.py +0 -0
  126. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/streaming/langserve.py +0 -0
  127. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/streaming/stream_lookup.py +0 -0
  128. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/streaming/streaming.py +0 -0
  129. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/summarise/__init__.py +0 -0
  130. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/summarise/summarise.py +0 -0
  131. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/terraform/__init__.py +0 -0
  132. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/terraform/tfvars_editor.py +0 -0
  133. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/tools/__init__.py +0 -0
  134. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/tools/web_browser.py +0 -0
  135. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/utils/__init__.py +0 -0
  136. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/utils/api_key.py +0 -0
  137. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/utils/big_context.py +0 -0
  138. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/utils/config.py +0 -0
  139. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/utils/config_class.py +0 -0
  140. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/utils/config_schema.py +0 -0
  141. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/utils/gcp.py +0 -0
  142. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/utils/gcp_project.py +0 -0
  143. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/utils/parsers.py +0 -0
  144. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/utils/timedelta.py +0 -0
  145. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/utils/user_ids.py +0 -0
  146. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/utils/version.py +0 -0
  147. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/vertex/__init__.py +0 -0
  148. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/vertex/extensions_call.py +0 -0
  149. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/vertex/extensions_class.py +0 -0
  150. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/vertex/genai_functions.py +0 -0
  151. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/vertex/init.py +0 -0
  152. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/vertex/memory_tools.py +0 -0
  153. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/vertex/safety.py +0 -0
  154. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo/vertex/type_dict_to_json.py +0 -0
  155. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo.egg-info/SOURCES.txt +0 -0
  156. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo.egg-info/dependency_links.txt +0 -0
  157. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo.egg-info/entry_points.txt +0 -0
  158. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo.egg-info/requires.txt +0 -0
  159. {sunholo-0.100.1 → sunholo-0.100.3}/sunholo.egg-info/top_level.txt +0 -0
  160. {sunholo-0.100.1 → sunholo-0.100.3}/tests/test_chat_history.py +0 -0
  161. {sunholo-0.100.1 → sunholo-0.100.3}/tests/test_config.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.100.1
3
+ Version: 0.100.3
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.3.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
@@ -1,6 +1,6 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
- version = '0.100.1'
3
+ version = '0.100.3'
4
4
 
5
5
  setup(
6
6
  name='sunholo',
@@ -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
@@ -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")
@@ -0,0 +1,178 @@
1
+ import asyncio
2
+ from typing import Callable, Any, AsyncGenerator, Dict
3
+ import time
4
+ import traceback
5
+ from ..custom_logging import setup_logging
6
+ from tenacity import AsyncRetrying, retry_if_exception_type, wait_random_exponential, stop_after_attempt
7
+
8
+ log = setup_logging("sunholo_AsyncTaskRunner")
9
+
10
+ class AsyncTaskRunner:
11
+ def __init__(self, retry_enabled=False, retry_kwargs=None):
12
+ self.tasks = []
13
+ self.retry_enabled = retry_enabled
14
+ self.retry_kwargs = retry_kwargs or {}
15
+
16
+ def add_task(self, func: Callable[..., Any], *args: Any):
17
+ """
18
+ Adds a task to the list of tasks to be executed.
19
+ """
20
+ log.info(f"Adding task: {func.__name__} with args: {args}")
21
+ self.tasks.append((func.__name__, func, args))
22
+
23
+ async def run_async_as_completed(self) -> AsyncGenerator[Dict[str, Any], None]:
24
+ """
25
+ Runs all tasks concurrently and yields results as they complete,
26
+ while periodically sending heartbeat messages.
27
+ """
28
+ log.info("Running tasks asynchronously and yielding results as they complete")
29
+
30
+ # Create a queue for inter-coroutine communication
31
+ queue = asyncio.Queue()
32
+
33
+ # List to keep track of all running tasks and their heartbeats
34
+ task_infos = []
35
+
36
+ # Start all tasks and their corresponding heartbeats
37
+ for name, func, args in self.tasks:
38
+ # Create an event to signal task completion to the heartbeat
39
+ 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)
43
+ task = asyncio.create_task(task_coro)
44
+
45
+ # Start the heartbeat coroutine
46
+ heartbeat_coro = self._send_heartbeat(name, completion_event, queue)
47
+ heartbeat_task = asyncio.create_task(heartbeat_coro)
48
+
49
+ # Store task information for management
50
+ task_infos.append({
51
+ 'name': name,
52
+ 'task': task,
53
+ 'heartbeat_task': heartbeat_task,
54
+ 'completion_event': completion_event
55
+ })
56
+
57
+ log.info(f"Started task '{name}' and its heartbeat")
58
+
59
+ 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
+ monitor = asyncio.create_task(self._monitor_tasks(task_infos, queue))
63
+
64
+ # Continuously yield messages from the queue until sentinel is received
65
+ while True:
66
+ message = await queue.get()
67
+ if message is None:
68
+ log.info("Received sentinel. Exiting message loop.")
69
+ break # Sentinel received, all tasks and heartbeats are done
70
+ log.info(f"Received message from queue: {message}")
71
+ yield message
72
+
73
+ # Wait for the monitor to finish
74
+ await monitor
75
+
76
+ log.info("All tasks and heartbeats have completed")
77
+
78
+ async def _monitor_tasks(self, task_infos, queue):
79
+ """
80
+ Monitors the tasks and heartbeats, and sends a sentinel to the queue when done.
81
+ """
82
+ # Wait for all main tasks to complete
83
+ main_tasks = [info['task'] for info in task_infos]
84
+ log.info("Monitor: Waiting for all main tasks to complete")
85
+ await asyncio.gather(*main_tasks, return_exceptions=True)
86
+ log.info("Monitor: All main tasks have completed")
87
+
88
+ # Cancel all heartbeat tasks
89
+ for info in task_infos:
90
+ info['heartbeat_task'].cancel()
91
+ try:
92
+ await info['heartbeat_task']
93
+ except asyncio.CancelledError:
94
+ pass
95
+ log.info(f"Monitor: Heartbeat for task '{info['name']}' has been canceled")
96
+
97
+ # Send a sentinel to indicate completion
98
+ await queue.put(None)
99
+ log.info("Monitor: Sent sentinel to queue")
100
+
101
+ async def _run_with_retries(self, name: str, func: Callable[..., Any], *args: Any, queue: asyncio.Queue, completion_event: asyncio.Event) -> None:
102
+ """
103
+ Executes a task with optional retries and sends completion or error messages to the queue.
104
+ """
105
+ try:
106
+ if self.retry_enabled:
107
+ retry_kwargs = {
108
+ 'wait': wait_random_exponential(multiplier=1, max=60),
109
+ 'stop': stop_after_attempt(5),
110
+ 'retry': retry_if_exception_type(Exception),
111
+ **self.retry_kwargs
112
+ }
113
+ async for attempt in AsyncRetrying(**retry_kwargs):
114
+ with attempt:
115
+ log.info(f"Starting task '{name}' with retry")
116
+ result = await self._execute_task(func, *args)
117
+ await queue.put({'type': 'task_complete', 'func_name': name, 'result': result})
118
+ log.info(f"Sent 'task_complete' message for task '{name}'")
119
+ return
120
+ else:
121
+ log.info(f"Starting task '{name}' with no retry")
122
+ result = await self._execute_task(func, *args)
123
+ await queue.put({'type': 'task_complete', 'func_name': name, 'result': result})
124
+ log.info(f"Sent 'task_complete' message for task '{name}'")
125
+ except Exception as e:
126
+ log.error(f"Error in task '{name}': {e}\n{traceback.format_exc()}")
127
+ 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
+ finally:
130
+ log.info(f"Task '{name}' completed.")
131
+ # Set the completion event after sending the message
132
+ completion_event.set()
133
+
134
+ async def _execute_task(self, func: Callable[..., Any], *args: Any) -> Any:
135
+ """
136
+ Executes the given task function and returns its result.
137
+
138
+ Args:
139
+ func (Callable): The callable to execute.
140
+ *args: Arguments to pass to the callable.
141
+
142
+ Returns:
143
+ Any: The result of the task.
144
+ """
145
+ if asyncio.iscoroutinefunction(func):
146
+ return await func(*args)
147
+ else:
148
+ return await asyncio.to_thread(func, *args)
149
+
150
+ async def _send_heartbeat(self, func_name: str, completion_event: asyncio.Event, queue: asyncio.Queue, interval: int = 2):
151
+ """
152
+ Sends periodic heartbeat updates to indicate the task is still in progress.
153
+
154
+ Args:
155
+ func_name (str): The name of the task function.
156
+ completion_event (asyncio.Event): Event to signal when the task is completed.
157
+ queue (asyncio.Queue): The queue to send heartbeat messages to.
158
+ interval (int): How frequently to send heartbeat messages (in seconds).
159
+ """
160
+ start_time = time.time()
161
+ log.info(f"Starting heartbeat for task '{func_name}' with interval {interval} seconds")
162
+ try:
163
+ while not completion_event.is_set():
164
+ await asyncio.sleep(interval)
165
+ elapsed_time = int(time.time() - start_time)
166
+ heartbeat_message = {
167
+ 'type': 'heartbeat',
168
+ 'name': func_name,
169
+ 'interval': interval,
170
+ 'elapsed_time': elapsed_time
171
+ }
172
+ log.info(f"Sending heartbeat for task '{func_name}', running for {elapsed_time} seconds")
173
+ await queue.put(heartbeat_message)
174
+ except asyncio.CancelledError:
175
+ log.info(f"Heartbeat for task '{func_name}' has been canceled")
176
+ finally:
177
+ log.info(f"Heartbeat for task '{func_name}' stopped")
178
+
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.100.1
3
+ Version: 0.100.3
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.3.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
@@ -1,138 +0,0 @@
1
- import asyncio
2
- from ..custom_logging import log
3
- import traceback
4
- from typing import Callable, Any, AsyncGenerator, Dict
5
- from tenacity import AsyncRetrying, retry_if_exception_type, wait_random_exponential, stop_after_attempt
6
-
7
- class AsyncTaskRunner:
8
- def __init__(self, retry_enabled=False, retry_kwargs=None):
9
- self.tasks = []
10
- self.retry_enabled = retry_enabled
11
- self.retry_kwargs = retry_kwargs or {}
12
-
13
- def add_task(self, func: Callable[..., Any], *args: Any):
14
- """Adds a task to the list of tasks to be executed."""
15
- log.info(f"Adding task: {func.__name__} with args: {args}")
16
- self.tasks.append((func.__name__, func, args))
17
-
18
- async def run_async_as_completed(self) -> AsyncGenerator[Dict[str, Any], None]:
19
- """
20
- Runs all tasks concurrently and yields results and heartbeats as they are produced.
21
- """
22
- log.info("Running tasks asynchronously and yielding results and heartbeats as they occur")
23
- queue = asyncio.Queue()
24
- tasks = {}
25
- completed_tasks = set()
26
-
27
- for name, func, args in self.tasks:
28
- coro = self._task_wrapper(name, func, args, queue)
29
- task = asyncio.create_task(coro)
30
- tasks[task] = name
31
-
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:
65
- """Wraps the task function to process its output and handle retries, while managing heartbeat updates."""
66
- async def run_func():
67
- if asyncio.iscoroutinefunction(func):
68
- return await func(*args)
69
- else:
70
- return await asyncio.to_thread(func, *args)
71
-
72
- # Start the heartbeat task
73
- heartbeat_task = asyncio.create_task(self._send_heartbeat(queue, name))
74
-
75
- try:
76
- if self.retry_enabled:
77
- retry_kwargs = {
78
- 'wait': wait_random_exponential(multiplier=1, max=60),
79
- 'stop': stop_after_attempt(5),
80
- 'retry': retry_if_exception_type(Exception),
81
- **self.retry_kwargs
82
- }
83
- async for attempt in AsyncRetrying(**retry_kwargs):
84
- with attempt:
85
- return await run_func()
86
- else:
87
- try:
88
- return await run_func()
89
- except Exception as e:
90
- log.error(f"Error in task {name}: {e}\n{traceback.format_exc()}")
91
- raise
92
- finally:
93
- # 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
100
-
101
- async def _send_heartbeat(self, queue: asyncio.Queue, func_name: str, interval=2):
102
- """
103
- Sends a periodic heartbeat to keep the task alive and update the spinner with elapsed time.
104
- """
105
- # Send the initial spinner HTML
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>'
111
- )
112
- log.info(f"Heartbeat started for task {func_name}")
113
-
114
- await queue.put({'type': 'heartbeat', 'func_name': func_name, 'token': spinner_html})
115
-
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})
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes