botrun-flow-lang 5.12.263__py3-none-any.whl → 6.2.21__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.
Files changed (89) hide show
  1. botrun_flow_lang/api/auth_api.py +39 -39
  2. botrun_flow_lang/api/auth_utils.py +183 -183
  3. botrun_flow_lang/api/botrun_back_api.py +65 -65
  4. botrun_flow_lang/api/flow_api.py +3 -3
  5. botrun_flow_lang/api/hatch_api.py +508 -508
  6. botrun_flow_lang/api/langgraph_api.py +816 -811
  7. botrun_flow_lang/api/langgraph_constants.py +11 -0
  8. botrun_flow_lang/api/line_bot_api.py +1484 -1484
  9. botrun_flow_lang/api/model_api.py +300 -300
  10. botrun_flow_lang/api/rate_limit_api.py +32 -32
  11. botrun_flow_lang/api/routes.py +79 -79
  12. botrun_flow_lang/api/search_api.py +53 -53
  13. botrun_flow_lang/api/storage_api.py +395 -395
  14. botrun_flow_lang/api/subsidy_api.py +290 -290
  15. botrun_flow_lang/api/subsidy_api_system_prompt.txt +109 -109
  16. botrun_flow_lang/api/user_setting_api.py +70 -70
  17. botrun_flow_lang/api/version_api.py +31 -31
  18. botrun_flow_lang/api/youtube_api.py +26 -26
  19. botrun_flow_lang/constants.py +13 -13
  20. botrun_flow_lang/langgraph_agents/agents/agent_runner.py +178 -178
  21. botrun_flow_lang/langgraph_agents/agents/agent_tools/step_planner.py +77 -77
  22. botrun_flow_lang/langgraph_agents/agents/checkpointer/firestore_checkpointer.py +666 -666
  23. botrun_flow_lang/langgraph_agents/agents/gov_researcher/GOV_RESEARCHER_PRD.md +192 -192
  24. botrun_flow_lang/langgraph_agents/agents/gov_researcher/gemini_subsidy_graph.py +460 -460
  25. botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_2_graph.py +1002 -1002
  26. botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_graph.py +822 -822
  27. botrun_flow_lang/langgraph_agents/agents/langgraph_react_agent.py +730 -723
  28. botrun_flow_lang/langgraph_agents/agents/search_agent_graph.py +864 -864
  29. botrun_flow_lang/langgraph_agents/agents/tools/__init__.py +4 -4
  30. botrun_flow_lang/langgraph_agents/agents/tools/gemini_code_execution.py +376 -376
  31. botrun_flow_lang/langgraph_agents/agents/util/gemini_grounding.py +66 -66
  32. botrun_flow_lang/langgraph_agents/agents/util/html_util.py +316 -316
  33. botrun_flow_lang/langgraph_agents/agents/util/img_util.py +336 -294
  34. botrun_flow_lang/langgraph_agents/agents/util/local_files.py +419 -419
  35. botrun_flow_lang/langgraph_agents/agents/util/mermaid_util.py +86 -86
  36. botrun_flow_lang/langgraph_agents/agents/util/model_utils.py +143 -143
  37. botrun_flow_lang/langgraph_agents/agents/util/pdf_analyzer.py +562 -486
  38. botrun_flow_lang/langgraph_agents/agents/util/pdf_cache.py +250 -250
  39. botrun_flow_lang/langgraph_agents/agents/util/pdf_processor.py +204 -204
  40. botrun_flow_lang/langgraph_agents/agents/util/perplexity_search.py +464 -464
  41. botrun_flow_lang/langgraph_agents/agents/util/plotly_util.py +59 -59
  42. botrun_flow_lang/langgraph_agents/agents/util/tavily_search.py +199 -199
  43. botrun_flow_lang/langgraph_agents/agents/util/usage_metadata.py +34 -0
  44. botrun_flow_lang/langgraph_agents/agents/util/youtube_util.py +90 -90
  45. botrun_flow_lang/langgraph_agents/cache/langgraph_botrun_cache.py +197 -197
  46. botrun_flow_lang/llm_agent/llm_agent.py +19 -19
  47. botrun_flow_lang/llm_agent/llm_agent_util.py +83 -83
  48. botrun_flow_lang/log/.gitignore +2 -2
  49. botrun_flow_lang/main.py +61 -61
  50. botrun_flow_lang/main_fast.py +51 -51
  51. botrun_flow_lang/mcp_server/__init__.py +10 -10
  52. botrun_flow_lang/mcp_server/default_mcp.py +854 -744
  53. botrun_flow_lang/models/nodes/utils.py +205 -205
  54. botrun_flow_lang/models/token_usage.py +34 -34
  55. botrun_flow_lang/requirements.txt +21 -21
  56. botrun_flow_lang/services/base/firestore_base.py +30 -30
  57. botrun_flow_lang/services/hatch/hatch_factory.py +11 -11
  58. botrun_flow_lang/services/hatch/hatch_fs_store.py +419 -419
  59. botrun_flow_lang/services/storage/storage_cs_store.py +206 -206
  60. botrun_flow_lang/services/storage/storage_factory.py +12 -12
  61. botrun_flow_lang/services/storage/storage_store.py +65 -65
  62. botrun_flow_lang/services/user_setting/user_setting_factory.py +9 -9
  63. botrun_flow_lang/services/user_setting/user_setting_fs_store.py +66 -66
  64. botrun_flow_lang/static/docs/tools/index.html +926 -926
  65. botrun_flow_lang/tests/api_functional_tests.py +1525 -1525
  66. botrun_flow_lang/tests/api_stress_test.py +357 -357
  67. botrun_flow_lang/tests/shared_hatch_tests.py +333 -333
  68. botrun_flow_lang/tests/test_botrun_app.py +46 -46
  69. botrun_flow_lang/tests/test_html_util.py +31 -31
  70. botrun_flow_lang/tests/test_img_analyzer.py +190 -190
  71. botrun_flow_lang/tests/test_img_util.py +39 -39
  72. botrun_flow_lang/tests/test_local_files.py +114 -114
  73. botrun_flow_lang/tests/test_mermaid_util.py +103 -103
  74. botrun_flow_lang/tests/test_pdf_analyzer.py +104 -104
  75. botrun_flow_lang/tests/test_plotly_util.py +151 -151
  76. botrun_flow_lang/tests/test_run_workflow_engine.py +65 -65
  77. botrun_flow_lang/tools/generate_docs.py +133 -133
  78. botrun_flow_lang/tools/templates/tools.html +153 -153
  79. botrun_flow_lang/utils/__init__.py +7 -7
  80. botrun_flow_lang/utils/botrun_logger.py +344 -344
  81. botrun_flow_lang/utils/clients/rate_limit_client.py +209 -209
  82. botrun_flow_lang/utils/clients/token_verify_client.py +153 -153
  83. botrun_flow_lang/utils/google_drive_utils.py +654 -654
  84. botrun_flow_lang/utils/langchain_utils.py +324 -324
  85. botrun_flow_lang/utils/yaml_utils.py +9 -9
  86. {botrun_flow_lang-5.12.263.dist-info → botrun_flow_lang-6.2.21.dist-info}/METADATA +6 -6
  87. botrun_flow_lang-6.2.21.dist-info/RECORD +104 -0
  88. botrun_flow_lang-5.12.263.dist-info/RECORD +0 -102
  89. {botrun_flow_lang-5.12.263.dist-info → botrun_flow_lang-6.2.21.dist-info}/WHEEL +0 -0
@@ -1,206 +1,206 @@
1
- from google.cloud import storage
2
- from google.cloud.exceptions import NotFound
3
- from google.oauth2 import service_account
4
- from io import BytesIO
5
- import os
6
- from typing import Optional, Tuple
7
- from datetime import datetime, timedelta, UTC
8
-
9
- from botrun_flow_lang.constants import HATCH_BUCKET_NAME
10
- from botrun_flow_lang.services.storage.storage_store import StorageStore
11
- from botrun_flow_lang.utils.botrun_logger import get_default_botrun_logger
12
-
13
- logger = get_default_botrun_logger()
14
-
15
-
16
- class StorageCsStore(StorageStore):
17
- def __init__(self, env_name: str):
18
- google_service_account_key_path = os.getenv(
19
- "GOOGLE_APPLICATION_CREDENTIALS_FOR_FASTAPI",
20
- "/app/keys/scoop-386004-d22d99a7afd9.json",
21
- )
22
- credentials = service_account.Credentials.from_service_account_file(
23
- google_service_account_key_path,
24
- scopes=["https://www.googleapis.com/auth/devstorage.full_control"],
25
- )
26
-
27
- self.storage_client = storage.Client(credentials=credentials)
28
- self.bucket_name = f"{HATCH_BUCKET_NAME}-{env_name}"
29
- self.bucket = self.create_bucket(self.bucket_name)
30
- if not self.bucket:
31
- raise Exception(f"Failed to create or get bucket: {self.bucket_name}")
32
-
33
- def create_bucket(self, bucket_name: str) -> Optional[storage.Bucket]:
34
- """創建新的 bucket,如果已存在則返回現有的,並確保 lifecycle rules 正確設定"""
35
- try:
36
- bucket = self.storage_client.bucket(bucket_name)
37
-
38
- desired_rules = [
39
- {
40
- "action": {"type": "Delete"},
41
- "condition": {"age": 365, "matchesPrefix": ["tmp/"]},
42
- },
43
- {
44
- "action": {"type": "Delete"},
45
- "condition": {"age": 7, "matchesPrefix": ["pdf-cache/"]},
46
- },
47
- ]
48
-
49
- if not bucket.exists():
50
- logger.info(f"Creating new bucket: {bucket_name}")
51
- # 設定 lifecycle rules
52
- bucket.lifecycle_rules = desired_rules
53
- # 創建 bucket
54
- bucket = self.storage_client.create_bucket(
55
- bucket, location="asia-east1"
56
- )
57
- logger.info(
58
- f"Created bucket {bucket_name} in asia-east1 and set lifecycle rules."
59
- )
60
- else:
61
- logger.info(
62
- f"Bucket {bucket_name} already exists. Checking lifecycle rules."
63
- )
64
- bucket.reload() # 獲取最新的 bucket metadata
65
-
66
- # google-cloud-storage 回傳的 rule 是 frozenset of dicts,需要轉換
67
- current_rules = [dict(rule) for rule in bucket.lifecycle_rules]
68
-
69
- if current_rules != desired_rules:
70
- logger.info(f"Updating lifecycle rules for bucket {bucket_name}")
71
- bucket.lifecycle_rules = desired_rules
72
- bucket.patch()
73
- logger.info(
74
- f"Successfully updated lifecycle rules for bucket {bucket_name}"
75
- )
76
- else:
77
- logger.info(
78
- f"Lifecycle rules for bucket {bucket_name} are already up-to-date."
79
- )
80
-
81
- return bucket
82
- except Exception as e:
83
- logger.error(f"Error creating or updating bucket {bucket_name}: {str(e)}")
84
- return None
85
-
86
- async def get_directory_sizes(self) -> dict:
87
- """
88
- 計算 bucket 中每個目錄的總檔案大小 (bytes) 與檔案數量,排除 tmp, html 目錄
89
-
90
- Returns:
91
- dict: 包含每個目錄資訊的字典,格式為 {directory_name: {"size": total_size_in_bytes, "file_count": count}}
92
- """
93
- try:
94
- # 初始化結果字典
95
- directory_info = {}
96
-
97
- # 列出所有 blobs
98
- blobs = list(self.bucket.list_blobs())
99
-
100
- # 計算每個目錄的大小和檔案數量
101
- for blob in blobs:
102
- # 跳過 tmp 目錄
103
- if blob.name.startswith("tmp/"):
104
- continue
105
- # 跳過 html 目錄
106
- if blob.name.startswith("html/"):
107
- continue
108
-
109
- # 從 blob 名稱中提取目錄名稱 (第一層目錄)
110
- parts = blob.name.split("/")
111
- if len(parts) >= 1:
112
- directory = parts[0]
113
-
114
- # 如果這是一個新目錄,初始化其資訊
115
- if directory not in directory_info:
116
- directory_info[directory] = {"size": 0, "file_count": 0}
117
-
118
- # 加上此 blob 的大小
119
- directory_info[directory]["size"] += blob.size
120
- # 增加檔案計數
121
- directory_info[directory]["file_count"] += 1
122
-
123
- return directory_info
124
- except Exception as e:
125
- logger.error(f"Error calculating directory sizes: {e}")
126
- return {}
127
-
128
- async def store_file(
129
- self,
130
- filepath: str,
131
- file_object: BytesIO,
132
- public: bool = False,
133
- content_type: str = None,
134
- ) -> Tuple[bool, Optional[str]]:
135
- try:
136
- blob = self.bucket.blob(filepath)
137
-
138
- # 設定 content_type 和其他 metadata
139
- if content_type:
140
- blob.content_type = content_type
141
- # 如果是圖片,設定為 inline 顯示並加入 cache control
142
- if content_type.startswith("image/"):
143
- blob.content_disposition = (
144
- 'inline; filename="' + filepath.split("/")[-1] + '"'
145
- )
146
- blob.cache_control = "public, max-age=3600, no-transform"
147
-
148
- # 上傳檔案
149
- blob.upload_from_file(file_object, rewind=True)
150
-
151
- # 確保 metadata 更新
152
- blob.patch()
153
-
154
- # 如果需要公開存取
155
- if public:
156
- blob.make_public()
157
- return True, blob.public_url
158
-
159
- return True, None
160
- except Exception as e:
161
- logger.error(f"Error storing file in Cloud Storage: {e}")
162
- return False, None
163
-
164
- async def get_public_url(self, filepath: str) -> Optional[str]:
165
- try:
166
- blob = self.bucket.blob(filepath)
167
- if blob.exists():
168
- return blob.public_url
169
- return None
170
- except Exception as e:
171
- logger.error(f"Error getting public URL: {e}")
172
- return None
173
-
174
- async def retrieve_file(self, filepath: str) -> Optional[BytesIO]:
175
- try:
176
- blob = self.bucket.blob(filepath)
177
- file_object = BytesIO()
178
- blob.download_to_file(file_object)
179
- file_object.seek(0) # Rewind the file object to the beginning
180
- return file_object
181
- except NotFound:
182
- logger.error(f"File not found in Cloud Storage: {filepath}")
183
- return None
184
- except Exception as e:
185
- logger.error(f"Error retrieving file from Cloud Storage: {e}")
186
- return None
187
-
188
- async def delete_file(self, filepath: str) -> bool:
189
- try:
190
- blob = self.bucket.blob(filepath)
191
- blob.delete()
192
- return True
193
- except NotFound:
194
- logger.error(f"File not found in Cloud Storage: {filepath}")
195
- return False
196
- except Exception as e:
197
- logger.error(f"Error deleting file from Cloud Storage: {e}")
198
- return False
199
-
200
- async def file_exists(self, filepath: str) -> bool:
201
- try:
202
- blob = self.bucket.blob(filepath)
203
- return blob.exists()
204
- except Exception as e:
205
- logger.error(f"Error checking file existence in Cloud Storage: {e}")
206
- return False
1
+ from google.cloud import storage
2
+ from google.cloud.exceptions import NotFound
3
+ from google.oauth2 import service_account
4
+ from io import BytesIO
5
+ import os
6
+ from typing import Optional, Tuple
7
+ from datetime import datetime, timedelta, UTC
8
+
9
+ from botrun_flow_lang.constants import HATCH_BUCKET_NAME
10
+ from botrun_flow_lang.services.storage.storage_store import StorageStore
11
+ from botrun_flow_lang.utils.botrun_logger import get_default_botrun_logger
12
+
13
+ logger = get_default_botrun_logger()
14
+
15
+
16
+ class StorageCsStore(StorageStore):
17
+ def __init__(self, env_name: str):
18
+ google_service_account_key_path = os.getenv(
19
+ "GOOGLE_APPLICATION_CREDENTIALS_FOR_FASTAPI",
20
+ "/app/keys/scoop-386004-d22d99a7afd9.json",
21
+ )
22
+ credentials = service_account.Credentials.from_service_account_file(
23
+ google_service_account_key_path,
24
+ scopes=["https://www.googleapis.com/auth/devstorage.full_control"],
25
+ )
26
+
27
+ self.storage_client = storage.Client(credentials=credentials)
28
+ self.bucket_name = f"{HATCH_BUCKET_NAME}-{env_name}"
29
+ self.bucket = self.create_bucket(self.bucket_name)
30
+ if not self.bucket:
31
+ raise Exception(f"Failed to create or get bucket: {self.bucket_name}")
32
+
33
+ def create_bucket(self, bucket_name: str) -> Optional[storage.Bucket]:
34
+ """創建新的 bucket,如果已存在則返回現有的,並確保 lifecycle rules 正確設定"""
35
+ try:
36
+ bucket = self.storage_client.bucket(bucket_name)
37
+
38
+ desired_rules = [
39
+ {
40
+ "action": {"type": "Delete"},
41
+ "condition": {"age": 365, "matchesPrefix": ["tmp/"]},
42
+ },
43
+ {
44
+ "action": {"type": "Delete"},
45
+ "condition": {"age": 7, "matchesPrefix": ["pdf-cache/"]},
46
+ },
47
+ ]
48
+
49
+ if not bucket.exists():
50
+ logger.info(f"Creating new bucket: {bucket_name}")
51
+ # 設定 lifecycle rules
52
+ bucket.lifecycle_rules = desired_rules
53
+ # 創建 bucket
54
+ bucket = self.storage_client.create_bucket(
55
+ bucket, location="asia-east1"
56
+ )
57
+ logger.info(
58
+ f"Created bucket {bucket_name} in asia-east1 and set lifecycle rules."
59
+ )
60
+ else:
61
+ logger.info(
62
+ f"Bucket {bucket_name} already exists. Checking lifecycle rules."
63
+ )
64
+ bucket.reload() # 獲取最新的 bucket metadata
65
+
66
+ # google-cloud-storage 回傳的 rule 是 frozenset of dicts,需要轉換
67
+ current_rules = [dict(rule) for rule in bucket.lifecycle_rules]
68
+
69
+ if current_rules != desired_rules:
70
+ logger.info(f"Updating lifecycle rules for bucket {bucket_name}")
71
+ bucket.lifecycle_rules = desired_rules
72
+ bucket.patch()
73
+ logger.info(
74
+ f"Successfully updated lifecycle rules for bucket {bucket_name}"
75
+ )
76
+ else:
77
+ logger.info(
78
+ f"Lifecycle rules for bucket {bucket_name} are already up-to-date."
79
+ )
80
+
81
+ return bucket
82
+ except Exception as e:
83
+ logger.error(f"Error creating or updating bucket {bucket_name}: {str(e)}")
84
+ return None
85
+
86
+ async def get_directory_sizes(self) -> dict:
87
+ """
88
+ 計算 bucket 中每個目錄的總檔案大小 (bytes) 與檔案數量,排除 tmp, html 目錄
89
+
90
+ Returns:
91
+ dict: 包含每個目錄資訊的字典,格式為 {directory_name: {"size": total_size_in_bytes, "file_count": count}}
92
+ """
93
+ try:
94
+ # 初始化結果字典
95
+ directory_info = {}
96
+
97
+ # 列出所有 blobs
98
+ blobs = list(self.bucket.list_blobs())
99
+
100
+ # 計算每個目錄的大小和檔案數量
101
+ for blob in blobs:
102
+ # 跳過 tmp 目錄
103
+ if blob.name.startswith("tmp/"):
104
+ continue
105
+ # 跳過 html 目錄
106
+ if blob.name.startswith("html/"):
107
+ continue
108
+
109
+ # 從 blob 名稱中提取目錄名稱 (第一層目錄)
110
+ parts = blob.name.split("/")
111
+ if len(parts) >= 1:
112
+ directory = parts[0]
113
+
114
+ # 如果這是一個新目錄,初始化其資訊
115
+ if directory not in directory_info:
116
+ directory_info[directory] = {"size": 0, "file_count": 0}
117
+
118
+ # 加上此 blob 的大小
119
+ directory_info[directory]["size"] += blob.size
120
+ # 增加檔案計數
121
+ directory_info[directory]["file_count"] += 1
122
+
123
+ return directory_info
124
+ except Exception as e:
125
+ logger.error(f"Error calculating directory sizes: {e}")
126
+ return {}
127
+
128
+ async def store_file(
129
+ self,
130
+ filepath: str,
131
+ file_object: BytesIO,
132
+ public: bool = False,
133
+ content_type: str = None,
134
+ ) -> Tuple[bool, Optional[str]]:
135
+ try:
136
+ blob = self.bucket.blob(filepath)
137
+
138
+ # 設定 content_type 和其他 metadata
139
+ if content_type:
140
+ blob.content_type = content_type
141
+ # 如果是圖片,設定為 inline 顯示並加入 cache control
142
+ if content_type.startswith("image/"):
143
+ blob.content_disposition = (
144
+ 'inline; filename="' + filepath.split("/")[-1] + '"'
145
+ )
146
+ blob.cache_control = "public, max-age=3600, no-transform"
147
+
148
+ # 上傳檔案
149
+ blob.upload_from_file(file_object, rewind=True)
150
+
151
+ # 確保 metadata 更新
152
+ blob.patch()
153
+
154
+ # 如果需要公開存取
155
+ if public:
156
+ blob.make_public()
157
+ return True, blob.public_url
158
+
159
+ return True, None
160
+ except Exception as e:
161
+ logger.error(f"Error storing file in Cloud Storage: {e}")
162
+ return False, None
163
+
164
+ async def get_public_url(self, filepath: str) -> Optional[str]:
165
+ try:
166
+ blob = self.bucket.blob(filepath)
167
+ if blob.exists():
168
+ return blob.public_url
169
+ return None
170
+ except Exception as e:
171
+ logger.error(f"Error getting public URL: {e}")
172
+ return None
173
+
174
+ async def retrieve_file(self, filepath: str) -> Optional[BytesIO]:
175
+ try:
176
+ blob = self.bucket.blob(filepath)
177
+ file_object = BytesIO()
178
+ blob.download_to_file(file_object)
179
+ file_object.seek(0) # Rewind the file object to the beginning
180
+ return file_object
181
+ except NotFound:
182
+ logger.error(f"File not found in Cloud Storage: {filepath}")
183
+ return None
184
+ except Exception as e:
185
+ logger.error(f"Error retrieving file from Cloud Storage: {e}")
186
+ return None
187
+
188
+ async def delete_file(self, filepath: str) -> bool:
189
+ try:
190
+ blob = self.bucket.blob(filepath)
191
+ blob.delete()
192
+ return True
193
+ except NotFound:
194
+ logger.error(f"File not found in Cloud Storage: {filepath}")
195
+ return False
196
+ except Exception as e:
197
+ logger.error(f"Error deleting file from Cloud Storage: {e}")
198
+ return False
199
+
200
+ async def file_exists(self, filepath: str) -> bool:
201
+ try:
202
+ blob = self.bucket.blob(filepath)
203
+ return blob.exists()
204
+ except Exception as e:
205
+ logger.error(f"Error checking file existence in Cloud Storage: {e}")
206
+ return False
@@ -1,12 +1,12 @@
1
- import os
2
- from dotenv import load_dotenv
3
-
4
- from botrun_flow_lang.services.storage.storage_cs_store import StorageCsStore
5
- from botrun_flow_lang.services.storage.storage_store import StorageStore
6
-
7
- load_dotenv()
8
-
9
-
10
- def storage_store_factory() -> StorageStore:
11
- env_name = os.getenv("HATCH_ENV_NAME", "botrun-hatch-dev")
12
- return StorageCsStore(env_name)
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ from botrun_flow_lang.services.storage.storage_cs_store import StorageCsStore
5
+ from botrun_flow_lang.services.storage.storage_store import StorageStore
6
+
7
+ load_dotenv()
8
+
9
+
10
+ def storage_store_factory() -> StorageStore:
11
+ env_name = os.getenv("HATCH_ENV_NAME", "botrun-hatch-dev")
12
+ return StorageCsStore(env_name)
@@ -1,65 +1,65 @@
1
- from abc import ABC, abstractmethod
2
- from typing import Optional, Tuple
3
- from io import BytesIO
4
-
5
-
6
- class StorageStore(ABC):
7
-
8
- @abstractmethod
9
- async def store_file(
10
- self,
11
- filepath: str,
12
- file_object: BytesIO,
13
- public: bool = False,
14
- content_type: str = None,
15
- ) -> Tuple[bool, Optional[str]]:
16
- """
17
- Store a file in the storage.
18
-
19
- :param filepath: The path where the file should be stored
20
- :param file_object: The file object to be stored (BytesIO)
21
- :param public: Whether the file should be publicly accessible
22
- :param content_type: The MIME type of the file
23
- :return: Tuple of (success, public_url if public=True else None)
24
- """
25
- pass
26
-
27
- @abstractmethod
28
- async def get_public_url(self, filepath: str) -> Optional[str]:
29
- """
30
- Get the public URL for a file.
31
-
32
- :param filepath: The path of the file
33
- :return: Public URL if the file exists and is public, None otherwise
34
- """
35
- pass
36
-
37
- @abstractmethod
38
- async def retrieve_file(self, filepath: str) -> Optional[BytesIO]:
39
- """
40
- Retrieve a file from the storage.
41
-
42
- :param filepath: The path of the file to retrieve
43
- :return: BytesIO object containing the file data if found, None otherwise
44
- """
45
- pass
46
-
47
- @abstractmethod
48
- async def delete_file(self, filepath: str) -> bool:
49
- """
50
- Delete a file from the storage.
51
-
52
- :param filepath: The path of the file to delete
53
- :return: True if the file was successfully deleted, False otherwise
54
- """
55
- pass
56
-
57
- @abstractmethod
58
- async def file_exists(self, filepath: str) -> bool:
59
- """
60
- Check if a file exists in the storage.
61
-
62
- :param filepath: The path of the file to check
63
- :return: True if the file exists, False otherwise
64
- """
65
- pass
1
+ from abc import ABC, abstractmethod
2
+ from typing import Optional, Tuple
3
+ from io import BytesIO
4
+
5
+
6
+ class StorageStore(ABC):
7
+
8
+ @abstractmethod
9
+ async def store_file(
10
+ self,
11
+ filepath: str,
12
+ file_object: BytesIO,
13
+ public: bool = False,
14
+ content_type: str = None,
15
+ ) -> Tuple[bool, Optional[str]]:
16
+ """
17
+ Store a file in the storage.
18
+
19
+ :param filepath: The path where the file should be stored
20
+ :param file_object: The file object to be stored (BytesIO)
21
+ :param public: Whether the file should be publicly accessible
22
+ :param content_type: The MIME type of the file
23
+ :return: Tuple of (success, public_url if public=True else None)
24
+ """
25
+ pass
26
+
27
+ @abstractmethod
28
+ async def get_public_url(self, filepath: str) -> Optional[str]:
29
+ """
30
+ Get the public URL for a file.
31
+
32
+ :param filepath: The path of the file
33
+ :return: Public URL if the file exists and is public, None otherwise
34
+ """
35
+ pass
36
+
37
+ @abstractmethod
38
+ async def retrieve_file(self, filepath: str) -> Optional[BytesIO]:
39
+ """
40
+ Retrieve a file from the storage.
41
+
42
+ :param filepath: The path of the file to retrieve
43
+ :return: BytesIO object containing the file data if found, None otherwise
44
+ """
45
+ pass
46
+
47
+ @abstractmethod
48
+ async def delete_file(self, filepath: str) -> bool:
49
+ """
50
+ Delete a file from the storage.
51
+
52
+ :param filepath: The path of the file to delete
53
+ :return: True if the file was successfully deleted, False otherwise
54
+ """
55
+ pass
56
+
57
+ @abstractmethod
58
+ async def file_exists(self, filepath: str) -> bool:
59
+ """
60
+ Check if a file exists in the storage.
61
+
62
+ :param filepath: The path of the file to check
63
+ :return: True if the file exists, False otherwise
64
+ """
65
+ pass
@@ -1,9 +1,9 @@
1
- import os
2
- from botrun_flow_lang.services.user_setting.user_setting_fs_store import (
3
- UserSettingFsStore,
4
- )
5
-
6
-
7
- def user_setting_store_factory():
8
- env_name = os.getenv("ENV_NAME", "dev")
9
- return UserSettingFsStore(env_name)
1
+ import os
2
+ from botrun_flow_lang.services.user_setting.user_setting_fs_store import (
3
+ UserSettingFsStore,
4
+ )
5
+
6
+
7
+ def user_setting_store_factory():
8
+ env_name = os.getenv("ENV_NAME", "dev")
9
+ return UserSettingFsStore(env_name)