solace-agent-mesh 0.0.1__py3-none-any.whl → 0.1.0__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.

Potentially problematic release.


This version of solace-agent-mesh might be problematic. Click here for more details.

Files changed (172) hide show
  1. solace_agent_mesh/__init__.py +0 -3
  2. solace_agent_mesh/agents/__init__.py +0 -0
  3. solace_agent_mesh/agents/base_agent_component.py +224 -0
  4. solace_agent_mesh/agents/global/__init__.py +0 -0
  5. solace_agent_mesh/agents/global/actions/__init__.py +0 -0
  6. solace_agent_mesh/agents/global/actions/agent_state_change.py +54 -0
  7. solace_agent_mesh/agents/global/actions/clear_history.py +32 -0
  8. solace_agent_mesh/agents/global/actions/convert_file_to_markdown.py +160 -0
  9. solace_agent_mesh/agents/global/actions/create_file.py +70 -0
  10. solace_agent_mesh/agents/global/actions/error_action.py +45 -0
  11. solace_agent_mesh/agents/global/actions/plantuml_diagram.py +93 -0
  12. solace_agent_mesh/agents/global/actions/plotly_graph.py +117 -0
  13. solace_agent_mesh/agents/global/actions/retrieve_file.py +51 -0
  14. solace_agent_mesh/agents/global/global_agent_component.py +38 -0
  15. solace_agent_mesh/agents/image_processing/__init__.py +0 -0
  16. solace_agent_mesh/agents/image_processing/actions/__init__.py +0 -0
  17. solace_agent_mesh/agents/image_processing/actions/create_image.py +75 -0
  18. solace_agent_mesh/agents/image_processing/actions/describe_image.py +115 -0
  19. solace_agent_mesh/agents/image_processing/image_processing_agent_component.py +23 -0
  20. solace_agent_mesh/agents/slack/__init__.py +1 -0
  21. solace_agent_mesh/agents/slack/actions/__init__.py +1 -0
  22. solace_agent_mesh/agents/slack/actions/post_message.py +177 -0
  23. solace_agent_mesh/agents/slack/slack_agent_component.py +59 -0
  24. solace_agent_mesh/agents/web_request/__init__.py +0 -0
  25. solace_agent_mesh/agents/web_request/actions/__init__.py +0 -0
  26. solace_agent_mesh/agents/web_request/actions/do_image_search.py +84 -0
  27. solace_agent_mesh/agents/web_request/actions/do_news_search.py +47 -0
  28. solace_agent_mesh/agents/web_request/actions/do_suggestion_search.py +34 -0
  29. solace_agent_mesh/agents/web_request/actions/do_web_request.py +134 -0
  30. solace_agent_mesh/agents/web_request/actions/download_file.py +69 -0
  31. solace_agent_mesh/agents/web_request/web_request_agent_component.py +33 -0
  32. solace_agent_mesh/cli/__init__.py +1 -0
  33. solace_agent_mesh/cli/commands/__init__.py +0 -0
  34. solace_agent_mesh/cli/commands/add/__init__.py +3 -0
  35. solace_agent_mesh/cli/commands/add/add.py +88 -0
  36. solace_agent_mesh/cli/commands/add/agent.py +110 -0
  37. solace_agent_mesh/cli/commands/add/copy_from_plugin.py +90 -0
  38. solace_agent_mesh/cli/commands/add/gateway.py +221 -0
  39. solace_agent_mesh/cli/commands/build.py +631 -0
  40. solace_agent_mesh/cli/commands/chat/__init__.py +3 -0
  41. solace_agent_mesh/cli/commands/chat/chat.py +361 -0
  42. solace_agent_mesh/cli/commands/config.py +29 -0
  43. solace_agent_mesh/cli/commands/init/__init__.py +3 -0
  44. solace_agent_mesh/cli/commands/init/ai_provider_step.py +76 -0
  45. solace_agent_mesh/cli/commands/init/broker_step.py +102 -0
  46. solace_agent_mesh/cli/commands/init/builtin_agent_step.py +88 -0
  47. solace_agent_mesh/cli/commands/init/check_if_already_done.py +13 -0
  48. solace_agent_mesh/cli/commands/init/create_config_file_step.py +52 -0
  49. solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +96 -0
  50. solace_agent_mesh/cli/commands/init/file_service_step.py +73 -0
  51. solace_agent_mesh/cli/commands/init/init.py +114 -0
  52. solace_agent_mesh/cli/commands/init/project_structure_step.py +45 -0
  53. solace_agent_mesh/cli/commands/init/rest_api_step.py +50 -0
  54. solace_agent_mesh/cli/commands/init/web_ui_step.py +40 -0
  55. solace_agent_mesh/cli/commands/plugin/__init__.py +3 -0
  56. solace_agent_mesh/cli/commands/plugin/add.py +98 -0
  57. solace_agent_mesh/cli/commands/plugin/build.py +217 -0
  58. solace_agent_mesh/cli/commands/plugin/create.py +117 -0
  59. solace_agent_mesh/cli/commands/plugin/plugin.py +109 -0
  60. solace_agent_mesh/cli/commands/plugin/remove.py +71 -0
  61. solace_agent_mesh/cli/commands/run.py +68 -0
  62. solace_agent_mesh/cli/commands/visualizer.py +138 -0
  63. solace_agent_mesh/cli/config.py +81 -0
  64. solace_agent_mesh/cli/main.py +306 -0
  65. solace_agent_mesh/cli/utils.py +246 -0
  66. solace_agent_mesh/common/__init__.py +0 -0
  67. solace_agent_mesh/common/action.py +91 -0
  68. solace_agent_mesh/common/action_list.py +37 -0
  69. solace_agent_mesh/common/action_response.py +327 -0
  70. solace_agent_mesh/common/constants.py +3 -0
  71. solace_agent_mesh/common/mysql_database.py +40 -0
  72. solace_agent_mesh/common/postgres_database.py +79 -0
  73. solace_agent_mesh/common/prompt_templates.py +30 -0
  74. solace_agent_mesh/common/prompt_templates_unused_delete.py +161 -0
  75. solace_agent_mesh/common/stimulus_utils.py +152 -0
  76. solace_agent_mesh/common/time.py +24 -0
  77. solace_agent_mesh/common/utils.py +638 -0
  78. solace_agent_mesh/configs/agent_global.yaml +74 -0
  79. solace_agent_mesh/configs/agent_image_processing.yaml +82 -0
  80. solace_agent_mesh/configs/agent_slack.yaml +64 -0
  81. solace_agent_mesh/configs/agent_web_request.yaml +75 -0
  82. solace_agent_mesh/configs/conversation_to_file.yaml +56 -0
  83. solace_agent_mesh/configs/error_catcher.yaml +56 -0
  84. solace_agent_mesh/configs/monitor.yaml +0 -0
  85. solace_agent_mesh/configs/monitor_stim_and_errors_to_slack.yaml +106 -0
  86. solace_agent_mesh/configs/monitor_user_feedback.yaml +58 -0
  87. solace_agent_mesh/configs/orchestrator.yaml +241 -0
  88. solace_agent_mesh/configs/service_embedding.yaml +81 -0
  89. solace_agent_mesh/configs/service_llm.yaml +265 -0
  90. solace_agent_mesh/configs/visualize_websocket.yaml +55 -0
  91. solace_agent_mesh/gateway/__init__.py +0 -0
  92. solace_agent_mesh/gateway/components/__init__.py +0 -0
  93. solace_agent_mesh/gateway/components/gateway_base.py +41 -0
  94. solace_agent_mesh/gateway/components/gateway_input.py +265 -0
  95. solace_agent_mesh/gateway/components/gateway_output.py +289 -0
  96. solace_agent_mesh/gateway/identity/bamboohr_identity.py +18 -0
  97. solace_agent_mesh/gateway/identity/identity_base.py +10 -0
  98. solace_agent_mesh/gateway/identity/identity_provider.py +60 -0
  99. solace_agent_mesh/gateway/identity/no_identity.py +9 -0
  100. solace_agent_mesh/gateway/identity/passthru_identity.py +9 -0
  101. solace_agent_mesh/monitors/base_monitor_component.py +26 -0
  102. solace_agent_mesh/monitors/feedback/user_feedback_monitor.py +75 -0
  103. solace_agent_mesh/monitors/stim_and_errors/stim_and_error_monitor.py +560 -0
  104. solace_agent_mesh/orchestrator/__init__.py +0 -0
  105. solace_agent_mesh/orchestrator/action_manager.py +225 -0
  106. solace_agent_mesh/orchestrator/components/__init__.py +0 -0
  107. solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +54 -0
  108. solace_agent_mesh/orchestrator/components/orchestrator_action_response_component.py +179 -0
  109. solace_agent_mesh/orchestrator/components/orchestrator_register_component.py +107 -0
  110. solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +477 -0
  111. solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +246 -0
  112. solace_agent_mesh/orchestrator/orchestrator_main.py +166 -0
  113. solace_agent_mesh/orchestrator/orchestrator_prompt.py +410 -0
  114. solace_agent_mesh/services/__init__.py +0 -0
  115. solace_agent_mesh/services/authorization/providers/base_authorization_provider.py +56 -0
  116. solace_agent_mesh/services/bamboo_hr_service/__init__.py +3 -0
  117. solace_agent_mesh/services/bamboo_hr_service/bamboo_hr.py +182 -0
  118. solace_agent_mesh/services/common/__init__.py +4 -0
  119. solace_agent_mesh/services/common/auto_expiry.py +45 -0
  120. solace_agent_mesh/services/common/singleton.py +18 -0
  121. solace_agent_mesh/services/file_service/__init__.py +14 -0
  122. solace_agent_mesh/services/file_service/file_manager/__init__.py +0 -0
  123. solace_agent_mesh/services/file_service/file_manager/bucket_file_manager.py +149 -0
  124. solace_agent_mesh/services/file_service/file_manager/file_manager_base.py +162 -0
  125. solace_agent_mesh/services/file_service/file_manager/memory_file_manager.py +64 -0
  126. solace_agent_mesh/services/file_service/file_manager/volume_file_manager.py +106 -0
  127. solace_agent_mesh/services/file_service/file_service.py +432 -0
  128. solace_agent_mesh/services/file_service/file_service_constants.py +54 -0
  129. solace_agent_mesh/services/file_service/file_transformations.py +131 -0
  130. solace_agent_mesh/services/file_service/file_utils.py +322 -0
  131. solace_agent_mesh/services/file_service/transformers/__init__.py +5 -0
  132. solace_agent_mesh/services/history_service/__init__.py +3 -0
  133. solace_agent_mesh/services/history_service/history_providers/__init__.py +0 -0
  134. solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +78 -0
  135. solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +167 -0
  136. solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +163 -0
  137. solace_agent_mesh/services/history_service/history_service.py +139 -0
  138. solace_agent_mesh/services/llm_service/components/llm_request_component.py +293 -0
  139. solace_agent_mesh/services/llm_service/components/llm_service_component_base.py +152 -0
  140. solace_agent_mesh/services/middleware_service/__init__.py +0 -0
  141. solace_agent_mesh/services/middleware_service/middleware_service.py +20 -0
  142. solace_agent_mesh/templates/action.py +38 -0
  143. solace_agent_mesh/templates/agent.py +29 -0
  144. solace_agent_mesh/templates/agent.yaml +70 -0
  145. solace_agent_mesh/templates/gateway-config-template.yaml +6 -0
  146. solace_agent_mesh/templates/gateway-default-config.yaml +28 -0
  147. solace_agent_mesh/templates/gateway-flows.yaml +81 -0
  148. solace_agent_mesh/templates/gateway-header.yaml +16 -0
  149. solace_agent_mesh/templates/gateway_base.py +15 -0
  150. solace_agent_mesh/templates/gateway_input.py +98 -0
  151. solace_agent_mesh/templates/gateway_output.py +71 -0
  152. solace_agent_mesh/templates/plugin-pyproject.toml +30 -0
  153. solace_agent_mesh/templates/rest-api-default-config.yaml +23 -0
  154. solace_agent_mesh/templates/rest-api-flows.yaml +80 -0
  155. solace_agent_mesh/templates/slack-default-config.yaml +9 -0
  156. solace_agent_mesh/templates/slack-flows.yaml +90 -0
  157. solace_agent_mesh/templates/solace-agent-mesh-default.yaml +77 -0
  158. solace_agent_mesh/templates/solace-agent-mesh-plugin-default.yaml +8 -0
  159. solace_agent_mesh/templates/web-default-config.yaml +5 -0
  160. solace_agent_mesh/templates/web-flows.yaml +86 -0
  161. solace_agent_mesh/tools/__init__.py +0 -0
  162. solace_agent_mesh/tools/components/__init__.py +0 -0
  163. solace_agent_mesh/tools/components/conversation_formatter.py +111 -0
  164. solace_agent_mesh/tools/components/file_resolver_component.py +58 -0
  165. solace_agent_mesh/tools/config/runtime_config.py +26 -0
  166. solace_agent_mesh-0.1.0.dist-info/METADATA +179 -0
  167. solace_agent_mesh-0.1.0.dist-info/RECORD +170 -0
  168. solace_agent_mesh-0.1.0.dist-info/entry_points.txt +3 -0
  169. solace_agent_mesh-0.0.1.dist-info/licenses/LICENSE.txt → solace_agent_mesh-0.1.0.dist-info/licenses/LICENSE +1 -2
  170. solace_agent_mesh-0.0.1.dist-info/METADATA +0 -51
  171. solace_agent_mesh-0.0.1.dist-info/RECORD +0 -5
  172. {solace_agent_mesh-0.0.1.dist-info → solace_agent_mesh-0.1.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,45 @@
1
+ from abc import ABC, abstractmethod
2
+ import time
3
+ import threading
4
+
5
+ from solace_ai_connector.common.log import log
6
+
7
+ class AutoExpiry(ABC):
8
+ _expiry_thread = None
9
+ expiration_check_interval = None
10
+ _stop_expiry_thread = None
11
+
12
+
13
+ def _start_auto_expiry_thread(self, expiration_check_interval):
14
+ """Starts a background thread to handle auto-expiry."""
15
+ self.expiration_check_interval = expiration_check_interval
16
+ self._stop_expiry_thread = threading.Event()
17
+ self._expiry_thread = threading.Thread(
18
+ target=self._auto_expiry_task, daemon=True
19
+ )
20
+ self._expiry_thread.start()
21
+
22
+ def _auto_expiry_task(self):
23
+ """Background task to check and delete expired items."""
24
+ while not self._stop_expiry_thread.is_set():
25
+ try:
26
+ self._delete_expired_items()
27
+ except Exception as e:
28
+ log.error(f"Error during auto-expiry process: {e}")
29
+ time.sleep(self.expiration_check_interval)
30
+
31
+ @abstractmethod
32
+ def _delete_expired_items(self):
33
+ """Checks all item and deletes those that have exceeded max_time_to_live."""
34
+ raise NotImplementedError
35
+
36
+ def stop_auto_expiry(self):
37
+ """Stops the auto-expiry background thread."""
38
+ if self._stop_expiry_thread:
39
+ self._stop_expiry_thread.set()
40
+ if self._expiry_thread and self._expiry_thread.is_alive():
41
+ self._expiry_thread.join()
42
+
43
+ def __del__(self):
44
+ """Ensure the expiry thread stops when the service is destroyed."""
45
+ self.stop_auto_expiry()
@@ -0,0 +1,18 @@
1
+ from .auto_expiry import AutoExpiry
2
+
3
+ # Singleton pattern - Ensuring that only one instance of the class is created per given identifier
4
+ class SingletonMeta(type):
5
+ _instances = {}
6
+
7
+ def __call__(cls, *args, **kwargs):
8
+ identifier = kwargs.get("identifier", "default")
9
+ if cls not in cls._instances:
10
+ cls._instances[cls] = {}
11
+ if identifier not in cls._instances[cls]:
12
+ instance = super().__call__(*args, **kwargs)
13
+ cls._instances[cls][identifier] = instance
14
+ return cls._instances[cls][identifier]
15
+
16
+
17
+ class AutoExpirySingletonMeta(SingletonMeta, type(AutoExpiry)):
18
+ pass
@@ -0,0 +1,14 @@
1
+ from .file_service import FileService, FileServicePermissionError, FS_URL_REGEX
2
+ from .file_utils import Types
3
+ from .file_transformations import LLM_QUERY_OPTIONS, TRANSFORMERS
4
+ from .file_service_constants import FS_PROTOCOL
5
+
6
+ __all__ = [
7
+ "FileService",
8
+ "FS_PROTOCOL",
9
+ "Types",
10
+ "FileServicePermissionError",
11
+ "FS_URL_REGEX",
12
+ "LLM_QUERY_OPTIONS",
13
+ "TRANSFORMERS"
14
+ ]
@@ -0,0 +1,149 @@
1
+ import boto3
2
+ import os
3
+ import json
4
+ from io import BytesIO
5
+ from botocore.exceptions import NoCredentialsError, ClientError
6
+
7
+ from .file_manager_base import FileManagerBase
8
+
9
+
10
+ class BucketFileManager(FileManagerBase):
11
+ def __init__(self, config, ttl):
12
+ self.config = config
13
+ self.ttl = ttl
14
+ self.bucket_name = config.get("bucket_name")
15
+ self.boto3_config = config.get("boto3_config", {})
16
+ self.endpoint_url = config.get("endpoint_url", None)
17
+ session = boto3.Session(**self.boto3_config)
18
+ s3_resource = session.resource("s3", endpoint_url=self.endpoint_url)
19
+ try:
20
+ if not self.bucket_name:
21
+ raise Exception("Bucket name not provided.")
22
+
23
+ bucket = s3_resource.Bucket(self.bucket_name)
24
+
25
+ # Check if the bucket exists by attempting to load it
26
+ bucket.load()
27
+ self.bucket = bucket
28
+
29
+ except ClientError as e:
30
+ # Handle the case where the bucket doesn't exist or access is denied
31
+ error_code = e.response["Error"]["Code"]
32
+ if error_code == "404":
33
+ raise Exception((f"Bucket '{self.bucket_name}' does not exist."), e)
34
+ elif error_code == "403":
35
+ raise Exception(
36
+ (f"Access to bucket '{self.bucket_name}' is denied."), e
37
+ )
38
+ else:
39
+ raise Exception(f"Unexpected error: {e}")
40
+
41
+ def _save_metadata(self, file_signature: str, metadata: dict):
42
+ metadata_key = self._get_metadata_name(file_signature)
43
+ metadata_content = json.dumps(metadata, indent=4)
44
+
45
+ try:
46
+ self.bucket.put_object(
47
+ Key=metadata_key,
48
+ Body=metadata_content,
49
+ ContentType="application/json",
50
+ )
51
+ except (NoCredentialsError, ClientError) as e:
52
+ raise RuntimeError(f"Failed to save metadata to S3: {str(e)}")
53
+
54
+ def upload_from_buffer(self, buffer: bytes, file_name: str, **kwargs) -> dict:
55
+ file_signature = self._generate_file_signature(file_name)
56
+ metadata = self._create_metadata(file_signature, file_name, buffer, kwargs)
57
+
58
+ try:
59
+ self.bucket.put_object(
60
+ Key=file_signature,
61
+ Body=buffer,
62
+ ContentType=metadata.get("mime_type") or "application/octet-stream",
63
+ )
64
+ except (NoCredentialsError, ClientError) as e:
65
+ raise RuntimeError(f"Failed to upload file to S3: {str(e)}")
66
+
67
+ self._save_metadata(file_signature, metadata)
68
+
69
+ return metadata
70
+
71
+ def upload_from_file(self, file_path: str, **kwargs) -> dict:
72
+ if not os.path.isfile(file_path):
73
+ raise FileNotFoundError(f"The file {file_path} does not exist.")
74
+
75
+ file_name = os.path.basename(file_path)
76
+ file_signature = self._generate_file_signature(file_name)
77
+ content_type = kwargs.get("mime_type") or self._get_mime_type(file_name) or "application/octet-stream"
78
+
79
+ try:
80
+ with open(file_path, "rb") as file_data:
81
+ self.bucket.put_object(
82
+ Key=file_signature,
83
+ Body=file_data,
84
+ ContentType=content_type,
85
+ )
86
+ metadata = self._create_metadata(file_signature, file_name, file_data.read(), kwargs)
87
+ except (NoCredentialsError, ClientError) as e:
88
+ raise RuntimeError(f"Failed to upload file to S3: {str(e)}")
89
+
90
+ self._save_metadata(file_signature, metadata)
91
+
92
+ return metadata
93
+
94
+ def download_to_buffer(self, file_name: str) -> bytes:
95
+ try:
96
+ buffer = BytesIO()
97
+ obj = self.bucket.Object(file_name)
98
+ obj.download_fileobj(buffer)
99
+ buffer.seek(0)
100
+ return buffer.read()
101
+ except (NoCredentialsError, ClientError) as e:
102
+ raise RuntimeError(f"Failed to download file from S3: {str(e)}")
103
+
104
+ def download_to_file(self, file_name: str, destination_path: str):
105
+ buffer = self.download_to_buffer(file_name)
106
+
107
+ try:
108
+ with open(destination_path, "wb") as destination_file:
109
+ destination_file.write(buffer)
110
+ except IOError as e:
111
+ raise RuntimeError(f"Failed to write file to destination: {str(e)}")
112
+
113
+ def get_metadata(self, file_name: str) -> dict:
114
+ metadata_key = self._get_metadata_name(file_name)
115
+ meta_buffer = self.download_to_buffer(metadata_key)
116
+ return json.loads(meta_buffer)
117
+
118
+ def delete_by_name(self, file_name: str):
119
+ metadata_key = self._get_metadata_name(file_name)
120
+
121
+ try:
122
+ # the main file
123
+ file = self.bucket.Object(file_name)
124
+ # the metadata file
125
+ meta = self.bucket.Object(metadata_key)
126
+
127
+ # Delete the files
128
+ file.delete()
129
+ meta.delete()
130
+ except (NoCredentialsError, ClientError) as e:
131
+ raise RuntimeError(f"Failed to delete file from S3: {str(e)}")
132
+
133
+ def update_file_expiration(self, file_signature, expiration_timestamp):
134
+ metadata = self.get_metadata(file_signature)
135
+ metadata["expiration_timestamp"] = expiration_timestamp
136
+ self._save_metadata(file_signature, metadata)
137
+
138
+ def list_all_metadata(self) -> list:
139
+ all_metadata = []
140
+ try:
141
+ for obj in self.bucket.objects.all():
142
+ if obj.key.endswith(".metadata"):
143
+ meta_buffer = self.download_to_buffer(obj.key)
144
+ metadata = json.loads(meta_buffer)
145
+
146
+ all_metadata.append(metadata)
147
+ except (NoCredentialsError, ClientError) as e:
148
+ raise RuntimeError(f"Failed to list metadata from S3: {str(e)}")
149
+ return all_metadata
@@ -0,0 +1,162 @@
1
+ import uuid
2
+ import mimetypes
3
+ import re
4
+ import time
5
+ from abc import ABC, abstractmethod
6
+
7
+ from ..file_service_constants import FS_PROTOCOL, META_FILE_EXTENSION
8
+ from ..file_utils import get_file_schema_and_shape
9
+
10
+
11
+ MAX_NAME_LENGTH = 255 - (
12
+ len(META_FILE_EXTENSION) + len(FS_PROTOCOL) + 3 + 36 + 1
13
+ ) # 36 is the length of a UUID, 3 is ://
14
+
15
+
16
+ class FileManagerBase(ABC):
17
+ ttl: int
18
+
19
+ def _generate_file_signature(self, file_name: str) -> str:
20
+ """
21
+ Generate a file signature from the original file name.
22
+ """
23
+ file_signature = str(uuid.uuid4())
24
+ # Remove any leading or trailing whitespace
25
+ file_name = file_name.strip()
26
+ # Replace invalid characters and space/ampersand with underscores
27
+ cleaned_name = re.sub(r'[<>:"/\\|?*& ]', "_", file_name)
28
+ # replace unicode and binary characters with underscores
29
+ cleaned_name = re.sub(r"[^\x00-\x7F]+", "_", cleaned_name)
30
+ cleaned_name = re.sub(r"\\u[0-9a-fA-F]{4}", "_", cleaned_name)
31
+ # Replace multiple underscores with a single underscore
32
+ cleaned_name = re.sub(r"_+", "_", cleaned_name)
33
+ # Truncate the signature if it is too long
34
+ if len(cleaned_name) > MAX_NAME_LENGTH:
35
+ start_index = len(cleaned_name) - MAX_NAME_LENGTH
36
+ # Keep the latter portion of the name
37
+ cleaned_name = cleaned_name[start_index:]
38
+ signature = f"{file_signature}_{cleaned_name}"
39
+ return signature
40
+
41
+ def _get_url_from_signature(self, file_signature: str) -> str:
42
+ """
43
+ Generate a file URL from a file signature.
44
+ """
45
+ return f"{FS_PROTOCOL}://{file_signature}"
46
+
47
+ def _get_mime_type(self, file_name: str) -> str:
48
+ """
49
+ Get the MIME type of a file.
50
+ """
51
+ mime_type, _ = mimetypes.guess_type(file_name)
52
+ return mime_type
53
+
54
+ def _get_metadata_name(self, name: str) -> dict:
55
+ """
56
+ Get metadata from a file URL.
57
+ """
58
+ metadata_path = f"{name}{META_FILE_EXTENSION}"
59
+ return metadata_path
60
+
61
+ def _create_metadata(self, file_signature: str, file_name: str, file: bytes, metadata: dict):
62
+ """
63
+ Extend metadata with schema and shape if not present
64
+ Add file_size if not present
65
+ """
66
+
67
+ mime_type = self._get_mime_type(file_name)
68
+ file_url = self._get_url_from_signature(file_signature)
69
+
70
+ if not isinstance(metadata, dict):
71
+ metadata = {}
72
+ meta_copy = metadata.copy()
73
+
74
+ # Can not be provided by user
75
+ meta_copy["url"] = file_url
76
+
77
+ if "mime_type" not in meta_copy:
78
+ meta_copy["mime_type"] = mime_type
79
+
80
+ if "name" not in meta_copy:
81
+ meta_copy["name"] = file_name
82
+
83
+ if (
84
+ "schema-yaml" not in meta_copy
85
+ or "schema_yaml" not in meta_copy
86
+ or "shape" not in meta_copy
87
+ ):
88
+ schema, shape = get_file_schema_and_shape(file, meta_copy)
89
+ if "schema-yaml" not in meta_copy and schema:
90
+ meta_copy["schema-yaml"] = schema
91
+
92
+ if "shape" not in meta_copy and shape:
93
+ meta_copy["shape"] = shape
94
+
95
+ if "file_size" not in meta_copy:
96
+ meta_copy["file_size"] = len(file)
97
+
98
+ if "upload_timestamp" not in meta_copy:
99
+ meta_copy["upload_timestamp"] = time.time()
100
+
101
+ if "expiration_timestamp" not in meta_copy:
102
+ meta_copy["expiration_timestamp"] = meta_copy["upload_timestamp"] + self.ttl
103
+
104
+ return meta_copy
105
+
106
+ @abstractmethod
107
+ def update_file_expiration(self, file_signature: str, expiration_timestamp: float):
108
+ """
109
+ Update the expiration timestamp for a file.
110
+ """
111
+ pass
112
+
113
+ @abstractmethod
114
+ def upload_from_buffer(self, buffer: bytes, file_name: str, **kwargs) -> dict:
115
+ """
116
+ Upload a file from a buffer.
117
+ kwargs are added to metadata
118
+ """
119
+ pass
120
+
121
+ @abstractmethod
122
+ def upload_from_file(self, file_path: str, **kwargs) -> dict:
123
+ """
124
+ Upload a file from a file path.
125
+ kwargs are added to metadata
126
+ """
127
+ pass
128
+
129
+ @abstractmethod
130
+ def download_to_buffer(self, file_name: str) -> bytes:
131
+ """
132
+ Download a file to a buffer.
133
+ """
134
+ pass
135
+
136
+ @abstractmethod
137
+ def download_to_file(self, file_name: str, destination_path: str):
138
+ """
139
+ Download a file to a destination path.
140
+ """
141
+ pass
142
+
143
+ @abstractmethod
144
+ def delete_by_name(self, file_name: str):
145
+ """
146
+ Delete a file by name.
147
+ """
148
+ pass
149
+
150
+ @abstractmethod
151
+ def get_metadata(self, file_name: str) -> dict:
152
+ """
153
+ Get metadata for a file.
154
+ """
155
+ pass
156
+
157
+ @abstractmethod
158
+ def list_all_metadata(self) -> list:
159
+ """
160
+ List all file metadata in the storage.
161
+ """
162
+ pass
@@ -0,0 +1,64 @@
1
+ import os
2
+
3
+ from .file_manager_base import FileManagerBase
4
+
5
+
6
+ class MemoryFileManager(FileManagerBase):
7
+ storage = {}
8
+
9
+ def __init__(self, config, ttl):
10
+ self.config = config
11
+ self.ttl = ttl
12
+
13
+ def upload_from_buffer(self, buffer: bytes, file_name: str, **kwargs) -> dict:
14
+ file_signature = self._generate_file_signature(file_name)
15
+ metadata = self._create_metadata(
16
+ file_signature, file_name, buffer, kwargs
17
+ )
18
+ metadata_name = self._get_metadata_name(file_signature)
19
+ self.storage[file_signature] = buffer
20
+ self.storage[metadata_name] = metadata
21
+ return metadata
22
+
23
+ def upload_from_file(self, file_path: str, **kwargs) -> dict:
24
+ """
25
+ Upload a file from a file path.
26
+ kwargs are added to metadata
27
+ """
28
+ with open(file_path, "rb") as file:
29
+ buffer = file.read()
30
+ return self.upload_from_buffer(buffer, os.path.basename(file_path), **kwargs)
31
+
32
+ def download_to_buffer(self, file_name: str) -> bytes:
33
+ if not file_name in self.storage:
34
+ raise FileNotFoundError(f"The file {file_name} does not exist.")
35
+ return self.storage[file_name]
36
+
37
+ def download_to_file(self, file_name: str, destination_path: str):
38
+ with open(destination_path, "wb") as file:
39
+ file.write(self.download_to_buffer(file_name))
40
+
41
+ def delete_by_name(self, file_name: str):
42
+ metadata_name = self._get_metadata_name(file_name)
43
+ if file_name in self.storage:
44
+ del self.storage[file_name]
45
+ if metadata_name in self.storage:
46
+ del self.storage[metadata_name]
47
+
48
+ def get_metadata(self, file_name: str) -> dict:
49
+ metadata_name = self._get_metadata_name(file_name)
50
+ if metadata_name in self.storage:
51
+ return self.storage[metadata_name]
52
+ raise FileNotFoundError(f"The file {file_name} does not exist.")
53
+
54
+ def update_file_expiration(self, file_signature, expiration_timestamp):
55
+ metadata_name = self._get_metadata_name(file_signature)
56
+ if metadata_name in self.storage:
57
+ metadata = self.storage[metadata_name]
58
+ metadata["expiration_timestamp"] = expiration_timestamp
59
+ self.storage[metadata_name] = metadata
60
+ else:
61
+ raise FileNotFoundError(f"The file {file_signature} does not exist.")
62
+
63
+ def list_all_metadata(self) -> list:
64
+ return [data for key, data in self.storage.items() if key.endswith(".metadata")]
@@ -0,0 +1,106 @@
1
+ import os
2
+ import json
3
+
4
+ from .file_manager_base import FileManagerBase
5
+ from ..file_service_constants import FS_PROTOCOL
6
+
7
+ DEFAULT_DIRECTORY = f"/tmp/{FS_PROTOCOL}"
8
+
9
+
10
+ class VolumeFileManager(FileManagerBase):
11
+
12
+ def __init__(self, config, ttl):
13
+ self.config = config
14
+ self.ttl = ttl
15
+ self.shared_volume_directory = config.get("directory", DEFAULT_DIRECTORY)
16
+ if not os.path.exists(self.shared_volume_directory):
17
+ os.makedirs(self.shared_volume_directory)
18
+
19
+ def _save_metadata(self, file_path: str, metadata: dict):
20
+ metadata_path = self._get_metadata_name(file_path)
21
+ with open(metadata_path, "w", encoding="utf-8") as metadata_file:
22
+ json.dump(metadata, metadata_file, indent=4)
23
+
24
+ def upload_from_buffer(self, buffer: bytes, file_name: str, **kwargs) -> dict:
25
+ file_signature = self._generate_file_signature(file_name)
26
+ file_path = os.path.join(self.shared_volume_directory, file_signature)
27
+
28
+ metadata = self._create_metadata(file_signature, file_name, buffer, kwargs)
29
+
30
+ with open(file_path, "wb") as file:
31
+ file.write(buffer)
32
+
33
+ self._save_metadata(file_path, metadata)
34
+
35
+ return metadata
36
+
37
+ def upload_from_file(self, file_path: str, **kwargs) -> dict:
38
+ if not os.path.isfile(file_path):
39
+ raise FileNotFoundError(f"The file {file_path} does not exist.")
40
+
41
+ file_name = os.path.basename(file_path)
42
+
43
+ with open(file_path, "rb") as source_file:
44
+ buffer = source_file.read()
45
+
46
+ return self.upload_from_buffer(buffer, file_name, **kwargs)
47
+
48
+ def download_to_buffer(self, file_name: str) -> bytes:
49
+ file_path = os.path.join(self.shared_volume_directory, file_name)
50
+ if not os.path.exists(file_path):
51
+ raise FileNotFoundError(
52
+ f"The file at {file_name} does not exist in the shared volume."
53
+ )
54
+
55
+ with open(file_path, "rb") as file:
56
+ buffer = file.read()
57
+
58
+ return buffer
59
+
60
+ def download_to_file(self, file_name: str, destination_path: str):
61
+ buffer = self.download_to_buffer(file_name)
62
+ with open(destination_path, "wb") as destination_file:
63
+ destination_file.write(buffer)
64
+
65
+ def get_metadata(self, file_name: str) -> dict:
66
+ metadata_name = self._get_metadata_name(file_name)
67
+ metadata_path = os.path.join(self.shared_volume_directory, metadata_name)
68
+
69
+ if not os.path.exists(metadata_path):
70
+ raise FileNotFoundError(
71
+ f"The metadata for the file at {file_name} does not exist."
72
+ )
73
+
74
+ with open(metadata_path, "r", encoding="utf-8") as metadata_file:
75
+ metadata = json.load(metadata_file)
76
+
77
+ return metadata
78
+
79
+ def delete_by_name(self, file_name: str):
80
+ file_path = os.path.join(self.shared_volume_directory, file_name)
81
+ metadata_path = self._get_metadata_name(file_path)
82
+
83
+ # Delete the main file
84
+ if os.path.exists(file_path):
85
+ os.remove(file_path)
86
+ os.remove(metadata_path)
87
+ else:
88
+ raise FileNotFoundError(
89
+ f"The file at {file_name} does not exist in the shared volume."
90
+ )
91
+
92
+ def update_file_expiration(self, file_signature, expiration_timestamp):
93
+ metadata = self.get_metadata(file_signature)
94
+ metadata["expiration_timestamp"] = expiration_timestamp
95
+ file_path = os.path.join(self.shared_volume_directory, file_signature)
96
+ self._save_metadata(file_path, metadata)
97
+
98
+ def list_all_metadata(self) -> list:
99
+ all_metadata = []
100
+ for file_name in os.listdir(self.shared_volume_directory):
101
+ if file_name.endswith(".metadata"):
102
+ metadata_path = os.path.join(self.shared_volume_directory, file_name)
103
+ with open(metadata_path, "r", encoding="utf-8") as metadata_file:
104
+ metadata = json.load(metadata_file)
105
+ all_metadata.append(metadata)
106
+ return all_metadata