octostar-python-client 0.1.759__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 (257) hide show
  1. octostar/__init__.py +9 -0
  2. octostar/api/__init__.py +1 -0
  3. octostar/api/apps/__init__.py +0 -0
  4. octostar/api/apps/deploy_app.py +210 -0
  5. octostar/api/apps/execute_app_job.py +188 -0
  6. octostar/api/apps/get_app_logs.py +210 -0
  7. octostar/api/apps/get_apps_url.py +188 -0
  8. octostar/api/apps/get_job_logs.py +210 -0
  9. octostar/api/apps/get_job_progress.py +162 -0
  10. octostar/api/apps/kill_job.py +160 -0
  11. octostar/api/apps/list_app_jobs.py +276 -0
  12. octostar/api/apps/list_apps.py +251 -0
  13. octostar/api/apps/set_job_progress.py +216 -0
  14. octostar/api/apps/undeploy_app.py +160 -0
  15. octostar/api/metadata/__init__.py +0 -0
  16. octostar/api/metadata/get_version.py +232 -0
  17. octostar/api/metadata/get_whoami.py +232 -0
  18. octostar/api/notifications/__init__.py +0 -0
  19. octostar/api/notifications/delete_stream.py +222 -0
  20. octostar/api/notifications/get_subscriptions.py +240 -0
  21. octostar/api/notifications/publish_notification.py +275 -0
  22. octostar/api/notifications/pull_events_from_stream.py +282 -0
  23. octostar/api/notifications/push_event_to_stream.py +265 -0
  24. octostar/api/notifications/toast.py +264 -0
  25. octostar/api/ontology/__init__.py +0 -0
  26. octostar/api/ontology/fetch_ontology_data.py +275 -0
  27. octostar/api/ontology/get_ontologies.py +237 -0
  28. octostar/api/ontology/multi_query.py +297 -0
  29. octostar/api/ontology/query.py +276 -0
  30. octostar/api/pipeline/__init__.py +1 -0
  31. octostar/api/pipeline/get_processing_status.py +185 -0
  32. octostar/api/pipeline/update_processing_status.py +164 -0
  33. octostar/api/search/__init__.py +0 -0
  34. octostar/api/search/get_annotations.py +153 -0
  35. octostar/api/workspace_data/__init__.py +0 -0
  36. octostar/api/workspace_data/delete_blob.py +212 -0
  37. octostar/api/workspace_data/delete_entities.py +326 -0
  38. octostar/api/workspace_data/download_blob.py +235 -0
  39. octostar/api/workspace_data/get_attachment.py +336 -0
  40. octostar/api/workspace_data/get_files_tree.py +397 -0
  41. octostar/api/workspace_data/upload_blob.py +235 -0
  42. octostar/api/workspace_data/upsert_entities.py +284 -0
  43. octostar/api/workspace_permissions/__init__.py +0 -0
  44. octostar/api/workspace_permissions/get_permissions.py +325 -0
  45. octostar/api/workspace_tags/__init__.py +0 -0
  46. octostar/api/workspace_tags/delete_tag_from_entities.py +141 -0
  47. octostar/api/workspace_tags/tag_entities.py +180 -0
  48. octostar/client.py +492 -0
  49. octostar/errors.py +50 -0
  50. octostar/models/__init__.py +249 -0
  51. octostar/models/acknowledgement.py +74 -0
  52. octostar/models/acknowledgement_with_data.py +82 -0
  53. octostar/models/app_status.py +239 -0
  54. octostar/models/app_status_annotations.py +66 -0
  55. octostar/models/app_status_labels.py +69 -0
  56. octostar/models/app_with_url.py +82 -0
  57. octostar/models/child_processing_status.py +118 -0
  58. octostar/models/delete_entities_response_401.py +74 -0
  59. octostar/models/delete_entities_response_409.py +82 -0
  60. octostar/models/delete_entities_response_500.py +82 -0
  61. octostar/models/delete_stream_response_401.py +74 -0
  62. octostar/models/delete_tag_from_entities_response_401.py +74 -0
  63. octostar/models/deploy_app_json_body.py +90 -0
  64. octostar/models/deploy_app_json_body_secrets.py +65 -0
  65. octostar/models/deploy_app_response_200.py +98 -0
  66. octostar/models/deploy_app_response_200_data.py +60 -0
  67. octostar/models/deploy_app_response_400.py +82 -0
  68. octostar/models/deploy_app_response_403.py +82 -0
  69. octostar/models/deploy_app_response_404.py +82 -0
  70. octostar/models/deploy_app_response_409.py +82 -0
  71. octostar/models/deploy_app_response_500.py +82 -0
  72. octostar/models/entity.py +80 -0
  73. octostar/models/entity_response.py +99 -0
  74. octostar/models/entity_response_s3_urls.py +93 -0
  75. octostar/models/entity_response_s3_urls_additional_property.py +105 -0
  76. octostar/models/entity_response_s3_urls_additional_property_fields.py +114 -0
  77. octostar/models/execute_app_job_json_body.py +151 -0
  78. octostar/models/execute_app_job_json_body_annotation.py +65 -0
  79. octostar/models/execute_app_job_response_401.py +74 -0
  80. octostar/models/fetch_ontology_data_response_200.py +60 -0
  81. octostar/models/fetch_ontology_data_response_401.py +74 -0
  82. octostar/models/fetch_ontology_data_response_500.py +82 -0
  83. octostar/models/get_app_logs_response_401.py +74 -0
  84. octostar/models/get_app_logs_response_404.py +74 -0
  85. octostar/models/get_app_logs_response_500.py +82 -0
  86. octostar/models/get_apps_url_json_body.py +76 -0
  87. octostar/models/get_apps_url_response_401.py +74 -0
  88. octostar/models/get_apps_url_response_500.py +82 -0
  89. octostar/models/get_attachment_response_200.py +74 -0
  90. octostar/models/get_attachment_response_401.py +74 -0
  91. octostar/models/get_files_tree_response_200.py +106 -0
  92. octostar/models/get_files_tree_response_200_status.py +8 -0
  93. octostar/models/get_files_tree_response_400.py +111 -0
  94. octostar/models/get_files_tree_response_400_data.py +60 -0
  95. octostar/models/get_files_tree_response_400_status.py +8 -0
  96. octostar/models/get_files_tree_response_401.py +74 -0
  97. octostar/models/get_files_tree_response_500.py +111 -0
  98. octostar/models/get_files_tree_response_500_data.py +60 -0
  99. octostar/models/get_files_tree_response_500_status.py +8 -0
  100. octostar/models/get_job_logs_response_401.py +74 -0
  101. octostar/models/get_job_logs_response_404.py +74 -0
  102. octostar/models/get_job_logs_response_500.py +82 -0
  103. octostar/models/get_job_progress_response_401.py +74 -0
  104. octostar/models/get_object_response_401.py +74 -0
  105. octostar/models/get_ontologies_response_401.py +74 -0
  106. octostar/models/get_ontologies_response_500.py +81 -0
  107. octostar/models/get_permissions_response_200.py +98 -0
  108. octostar/models/get_permissions_response_400.py +82 -0
  109. octostar/models/get_permissions_response_401.py +74 -0
  110. octostar/models/get_permissions_response_500.py +82 -0
  111. octostar/models/get_processing_status_response_200.py +104 -0
  112. octostar/models/get_processing_status_response_200_data.py +87 -0
  113. octostar/models/get_processing_status_response_400.py +82 -0
  114. octostar/models/get_processing_status_response_500.py +82 -0
  115. octostar/models/get_subscriptions_response_200_item.py +74 -0
  116. octostar/models/get_version_response_200.py +74 -0
  117. octostar/models/get_version_response_404.py +74 -0
  118. octostar/models/get_whoami_response_200.py +129 -0
  119. octostar/models/get_whoami_response_401.py +74 -0
  120. octostar/models/insert_entity.py +114 -0
  121. octostar/models/insert_entity_base.py +266 -0
  122. octostar/models/insert_entity_relationships_item.py +107 -0
  123. octostar/models/insert_entity_request.py +94 -0
  124. octostar/models/internal_server_error.py +82 -0
  125. octostar/models/job_execution_result.py +146 -0
  126. octostar/models/job_status.py +196 -0
  127. octostar/models/job_status_labels.py +60 -0
  128. octostar/models/job_with_url.py +82 -0
  129. octostar/models/kill_job_response_401.py +74 -0
  130. octostar/models/list_app_jobs_response_401.py +74 -0
  131. octostar/models/list_app_jobs_response_500.py +82 -0
  132. octostar/models/list_apps_response_401.py +74 -0
  133. octostar/models/list_apps_response_500.py +82 -0
  134. octostar/models/multi_query_json_body.py +100 -0
  135. octostar/models/multi_query_json_body_queries_item.py +80 -0
  136. octostar/models/multi_query_response_400.py +82 -0
  137. octostar/models/multi_query_response_401.py +74 -0
  138. octostar/models/not_found_error.py +74 -0
  139. octostar/models/octostar_event.py +96 -0
  140. octostar/models/octostar_event_octostar_payload.py +100 -0
  141. octostar/models/octostar_event_octostar_payload_level.py +11 -0
  142. octostar/models/os_notification.py +122 -0
  143. octostar/models/processing_status.py +262 -0
  144. octostar/models/processing_status_code.py +14 -0
  145. octostar/models/progress_request.py +73 -0
  146. octostar/models/publish_notification_response_401.py +74 -0
  147. octostar/models/pull_events_from_stream_response_401.py +74 -0
  148. octostar/models/push_event_to_stream_response_401.py +74 -0
  149. octostar/models/query_json_body.py +101 -0
  150. octostar/models/query_json_body_params.py +60 -0
  151. octostar/models/query_response_400.py +82 -0
  152. octostar/models/query_response_401.py +74 -0
  153. octostar/models/set_job_progress_response_401.py +74 -0
  154. octostar/models/string_to_value_label_map.py +99 -0
  155. octostar/models/string_to_value_label_map_data.py +89 -0
  156. octostar/models/string_to_value_label_map_data_additional_property.py +80 -0
  157. octostar/models/successful_get_tags.py +103 -0
  158. octostar/models/successful_insertion.py +98 -0
  159. octostar/models/tag_entities_response_401.py +74 -0
  160. octostar/models/toast_level.py +11 -0
  161. octostar/models/toast_response_401.py +74 -0
  162. octostar/models/undeploy_app_response_401.py +74 -0
  163. octostar/models/update_processing_status_response_200.py +82 -0
  164. octostar/models/update_processing_status_response_400.py +82 -0
  165. octostar/models/update_processing_status_response_500.py +82 -0
  166. octostar/models/upsert_entities_response_401.py +74 -0
  167. octostar/models/upsert_entity.py +114 -0
  168. octostar/models/upsert_entity_base.py +266 -0
  169. octostar/models/upsert_entity_relationships_item.py +107 -0
  170. octostar/py.typed +1 -0
  171. octostar/types.py +54 -0
  172. octostar/utils/__init__.py +15 -0
  173. octostar/utils/chat/__init__.py +0 -0
  174. octostar/utils/chat/chat.py +513 -0
  175. octostar/utils/chat/detokenize.py +105 -0
  176. octostar/utils/chat/get_default_model.py +50 -0
  177. octostar/utils/chat/list_models.py +91 -0
  178. octostar/utils/chat/tokenize.py +105 -0
  179. octostar/utils/commons.py +226 -0
  180. octostar/utils/exceptions.py +134 -0
  181. octostar/utils/jobs/__init__.py +0 -0
  182. octostar/utils/jobs/apps/__init__.py +0 -0
  183. octostar/utils/jobs/apps/deploy_app.py +81 -0
  184. octostar/utils/jobs/apps/execute_app_job.py +114 -0
  185. octostar/utils/jobs/apps/get_app_logs.py +113 -0
  186. octostar/utils/jobs/apps/get_app_secret.py +102 -0
  187. octostar/utils/jobs/apps/get_apps_url.py +73 -0
  188. octostar/utils/jobs/apps/list_app_jobs.py +62 -0
  189. octostar/utils/jobs/apps/list_apps.py +126 -0
  190. octostar/utils/jobs/apps/undeploy_app.py +48 -0
  191. octostar/utils/jobs/get_job_logs.py +113 -0
  192. octostar/utils/jobs/get_job_progress.py +76 -0
  193. octostar/utils/jobs/kill_job.py +47 -0
  194. octostar/utils/jobs/set_job_progress.py +67 -0
  195. octostar/utils/meta/__init__.py +0 -0
  196. octostar/utils/meta/get_version.py +30 -0
  197. octostar/utils/meta/get_whoami.py +30 -0
  198. octostar/utils/notifications/__init__.py +0 -0
  199. octostar/utils/notifications/delete_stream.py +58 -0
  200. octostar/utils/notifications/get_my_subscriptions.py +49 -0
  201. octostar/utils/notifications/publish_notification.py +73 -0
  202. octostar/utils/notifications/pull_event_from_stream.py +63 -0
  203. octostar/utils/notifications/pull_events_from_stream.py +64 -0
  204. octostar/utils/notifications/push_event_to_stream.py +109 -0
  205. octostar/utils/notifications/push_events_to_stream.py +137 -0
  206. octostar/utils/notifications/toast.py +92 -0
  207. octostar/utils/ontology/__init__.py +10 -0
  208. octostar/utils/ontology/fetch_ontology_data.py +141 -0
  209. octostar/utils/ontology/get_ontologies.py +55 -0
  210. octostar/utils/ontology/multiquery_ontology.py +287 -0
  211. octostar/utils/ontology/query_ontology.py +186 -0
  212. octostar/utils/pipeline/__init__.py +1 -0
  213. octostar/utils/pipeline/get_processing_status.py +230 -0
  214. octostar/utils/pipeline/update_processing_status.py +286 -0
  215. octostar/utils/search/__init__.py +11 -0
  216. octostar/utils/search/bulk_update.py +138 -0
  217. octostar/utils/search/count.py +117 -0
  218. octostar/utils/search/get_entity_annotations.py +304 -0
  219. octostar/utils/search/get_index_definition.py +111 -0
  220. octostar/utils/search/multi_search.py +129 -0
  221. octostar/utils/workspace/__init__.py +0 -0
  222. octostar/utils/workspace/delete_entities.py +247 -0
  223. octostar/utils/workspace/delete_entity.py +81 -0
  224. octostar/utils/workspace/delete_relationship.py +78 -0
  225. octostar/utils/workspace/delete_relationships.py +85 -0
  226. octostar/utils/workspace/delete_temporary_blob.py +85 -0
  227. octostar/utils/workspace/extract_entities.py +140 -0
  228. octostar/utils/workspace/get_filepath_from_item.py +85 -0
  229. octostar/utils/workspace/get_filepaths_from_items.py +100 -0
  230. octostar/utils/workspace/get_files_tree.py +102 -0
  231. octostar/utils/workspace/get_item_from_filepath.py +102 -0
  232. octostar/utils/workspace/get_items_from_filepaths.py +108 -0
  233. octostar/utils/workspace/linkcharts/__init__.py +0 -0
  234. octostar/utils/workspace/linkcharts/create_linkchart.py +241 -0
  235. octostar/utils/workspace/permissions/PermissionLevel.py +8 -0
  236. octostar/utils/workspace/permissions/__init__.py +1 -0
  237. octostar/utils/workspace/permissions/get_permissions.py +81 -0
  238. octostar/utils/workspace/read_attachment.py +284 -0
  239. octostar/utils/workspace/read_file.py +113 -0
  240. octostar/utils/workspace/read_temporary_blob.py +428 -0
  241. octostar/utils/workspace/saved_searches/__init__.py +0 -0
  242. octostar/utils/workspace/saved_searches/create_saved_search.py +183 -0
  243. octostar/utils/workspace/tags/__init__.py +0 -0
  244. octostar/utils/workspace/tags/delete_tag_from_entities.py +96 -0
  245. octostar/utils/workspace/tags/tag_entities.py +175 -0
  246. octostar/utils/workspace/upsert_entities.py +268 -0
  247. octostar/utils/workspace/upsert_entity.py +110 -0
  248. octostar/utils/workspace/upsert_relationship.py +128 -0
  249. octostar/utils/workspace/upsert_relationships.py +194 -0
  250. octostar/utils/workspace/write_attachment.py +263 -0
  251. octostar/utils/workspace/write_file.py +335 -0
  252. octostar/utils/workspace/write_temporary_blob.py +218 -0
  253. octostar_python_client-0.1.759.dist-info/METADATA +159 -0
  254. octostar_python_client-0.1.759.dist-info/RECORD +257 -0
  255. octostar_python_client-0.1.759.dist-info/WHEEL +5 -0
  256. octostar_python_client-0.1.759.dist-info/licenses/LICENSE +21 -0
  257. octostar_python_client-0.1.759.dist-info/top_level.txt +1 -0
@@ -0,0 +1,335 @@
1
+ from typing import Dict, Optional, Union
2
+ from io import BytesIO
3
+ import uuid
4
+ import logging
5
+
6
+ _logger = logging.getLogger(__name__)
7
+
8
+ from ...client import Client, get_default_client
9
+ from . import get_item_from_filepath
10
+ from . import get_filepath_from_item
11
+ from ..ontology import query_ontology
12
+ from .write_attachment import _create_entity_sync, _create_entity_async
13
+
14
+
15
+ def sync(
16
+ os_workspace: str,
17
+ filename: str,
18
+ filetype: str,
19
+ file: Union[str, bytes, BytesIO],
20
+ os_entity_uid: Union[str, None] = None,
21
+ os_parent_folder: Union[str, None] = None,
22
+ ontology_name: str = None,
23
+ fields: Optional[Dict] = None,
24
+ entity_type: str = "os_file",
25
+ client: Client = None,
26
+ ):
27
+ """
28
+ # Write a file to a workspace
29
+
30
+ This includes creating a local entity record representing the file as well as writing the file contents to storage so that they can be retrieved with read_file(). Note that using upsert_entity() only allows to save a record about a file, not the file content themselves.
31
+
32
+ ## Arguments
33
+ - `os_workspace`: The workspace ID in which to save the file
34
+ - `filename`: The absolute or relative filepath for the file.
35
+ It should generally begin with the workspace name followed by any folders and terminating
36
+ with the filename. Any non-existing folder in-between will be created. It can also be relative to os_parent_folder argument
37
+ if it begins with "./".
38
+ - `filetype`: The MIME type for the file
39
+ - `file`: The file contents, either an UTF-8 string or a bytes-like
40
+ - `os_entity_uid`: The entity ID for the file. Only necessary in case of an update to the file contents
41
+ - `ontology_name`: The name of the ontology
42
+ - `fields`: Optional dict of additional entity fields to include on the entity.
43
+ - `entity_type`: The ontological entity type for the file entity. Defaults to `os_file`.
44
+ - `client`: The Client with which to connect to Octostar. If None, the default one is used
45
+
46
+ ## Returns
47
+ The record of the written file
48
+
49
+ ## Raises
50
+ - `NotImplementedError`: If the operation is not supported for the ontology
51
+ - `ConnectionError`: If multiple (ambiguous) S3 urls are returned for this file
52
+ - `ApiConnectionError`: If the operation was unsuccessful on the server
53
+ """
54
+
55
+ def _write_file_nonrecursive(
56
+ os_workspace,
57
+ os_parent_folder,
58
+ os_entity_uid,
59
+ filename,
60
+ entity_type,
61
+ filetype,
62
+ file,
63
+ client,
64
+ extra_fields=None,
65
+ ):
66
+ entity_fields = {**(extra_fields or {})}
67
+ entity_fields.update(
68
+ {
69
+ "entity_id": os_entity_uid,
70
+ "entity_type": entity_type,
71
+ "entity_label": filename,
72
+ "os_item_name": filename,
73
+ "os_parent_folder": os_parent_folder,
74
+ "os_item_type": entity_type,
75
+ }
76
+ )
77
+ if filetype:
78
+ entity_fields["os_item_content_type"] = filetype
79
+
80
+ return _create_entity_sync(
81
+ os_workspace=os_workspace,
82
+ entity_fields=entity_fields,
83
+ file=file,
84
+ content_type=filetype,
85
+ client=client,
86
+ )
87
+
88
+ if not client:
89
+ client = get_default_client()
90
+ if not ontology_name:
91
+ ontology_name = client.ontology
92
+ if ontology_name != client.ontology:
93
+ raise NotImplementedError(
94
+ "This operation is currently only supported on the current ontology!"
95
+ )
96
+ if not os_entity_uid:
97
+ os_entity_uid = str(uuid.uuid4())
98
+ filepath = filename.split("/")[:-1]
99
+ filepath = [elem for elem in filepath if elem]
100
+ workspace_label = query_ontology.sync(
101
+ f"SELECT `entity_label` FROM `timbr`.`os_workspace` WHERE `entity_id`='{os_workspace}'",
102
+ client=client,
103
+ )
104
+ if not workspace_label or not workspace_label[0]:
105
+ raise ValueError("Workspace does not exist!")
106
+ workspace_label = workspace_label[0]["entity_label"]
107
+ if not filepath:
108
+ file_folder = os_workspace
109
+ lookup_base_path = None
110
+ else:
111
+ is_relative = len(filepath) > 0 and filepath[0] == "."
112
+ if is_relative:
113
+ filepath = filepath[1:]
114
+ if os_parent_folder:
115
+ os_parent_workspace = query_ontology.sync(
116
+ f"SELECT `os_workspace` FROM `timbr`.`os_folder` WHERE `entity_id`='{os_parent_folder}'",
117
+ client=client,
118
+ )
119
+ if not os_parent_workspace or not os_parent_workspace[0]:
120
+ raise ValueError("Parent folder does not exist!")
121
+ if os_parent_workspace[0]["os_workspace"] != os_workspace:
122
+ raise ValueError("Parent folder is not in the same workspace!")
123
+ file_folder = os_parent_folder
124
+ lookup_base_path = get_filepath_from_item.sync(
125
+ os_workspace, os_parent_folder, ontology_name, client
126
+ )
127
+ else:
128
+ file_folder = os_workspace
129
+ lookup_base_path = workspace_label
130
+ else:
131
+ if filepath[0] != workspace_label:
132
+ filepath.insert(0, workspace_label)
133
+ filepath = filepath[1:]
134
+ file_folder = os_workspace
135
+ lookup_base_path = workspace_label
136
+ folders_to_write = []
137
+ if lookup_base_path:
138
+ for i in range(len(filepath), 0, -1):
139
+ try:
140
+ lookup_path = lookup_base_path + "/" + "/".join(filepath[:i])
141
+ file_entity = get_item_from_filepath.sync(
142
+ os_workspace, lookup_path, False, ontology_name, client
143
+ )
144
+ if isinstance(file_entity, list):
145
+ file_entity = file_entity[0]
146
+ file_folder = file_entity["os_entity_uid"]
147
+ break
148
+ except ValueError:
149
+ folders_to_write.insert(0, filepath[i - 1])
150
+ if folders_to_write:
151
+ for folder in folders_to_write:
152
+ file_folder = _write_file_nonrecursive(
153
+ os_workspace,
154
+ file_folder,
155
+ str(uuid.uuid4()),
156
+ folder,
157
+ "os_folder",
158
+ None,
159
+ None,
160
+ client,
161
+ )["os_entity_uid"]
162
+ return _write_file_nonrecursive(
163
+ os_workspace,
164
+ file_folder,
165
+ os_entity_uid,
166
+ filename.rsplit("/", 1)[-1],
167
+ entity_type,
168
+ filetype,
169
+ file,
170
+ client,
171
+ extra_fields=fields,
172
+ )
173
+
174
+
175
+ async def asyncio(
176
+ os_workspace: str,
177
+ filename: str,
178
+ filetype: str,
179
+ file: Union[str, bytes, BytesIO],
180
+ os_entity_uid: Union[str, None] = None,
181
+ os_parent_folder: Union[str, None] = None,
182
+ ontology_name: str = None,
183
+ fields: Optional[Dict] = None,
184
+ entity_type: str = "os_file",
185
+ client: Client = None,
186
+ ):
187
+ """
188
+ # Write asynchronously file to a workspace
189
+
190
+ This includes creating a local entity record representing the file as well as writing the file contents to storage so that they can be retrieved with read_file(). Note that using upsert_entity() only allows to save a record about a file, not the file content themselves.
191
+
192
+ ## Arguments
193
+ - `os_workspace`: The workspace ID in which to save the file
194
+ - `filename`: The absolute or relative filepath for the file.
195
+ It should generally begin with the workspace name followed by any folders and terminating
196
+ with the filename. Any non-existing folder in-between will be created. It can also be relative to os_parent_folder argument
197
+ if it begins with "./".
198
+ - `filetype`: The MIME type for the file
199
+ - `file`: The file contents, either an UTF-8 string or a bytes-like
200
+ - `os_entity_uid`: The entity ID for the file. Only necessary in case of an update to the file contents
201
+ - `os_parent_folder`: The parent folder ID if filename is relative
202
+ - `ontology_name`: The name of the ontology
203
+ - `fields`: Optional dict of additional entity fields to include on the entity.
204
+ - `entity_type`: The ontological entity type for the file entity. Defaults to `os_file`.
205
+ - `client`: The Client with which to connect to Octostar. If None, the default one is used
206
+
207
+ ## Returns
208
+ The record of the written file
209
+
210
+ ## Raises
211
+ - `NotImplementedError`: If the operation is not supported for the ontology
212
+ - `ConnectionError`: If multiple (ambiguous) S3 urls are returned for this file
213
+ - `ApiConnectionError`: If the operation was unsuccessful on the server
214
+ """
215
+
216
+ async def _write_file_nonrecursive(
217
+ os_workspace,
218
+ os_parent_folder,
219
+ os_entity_uid,
220
+ filename,
221
+ entity_type,
222
+ filetype,
223
+ file,
224
+ client,
225
+ extra_fields=None,
226
+ ):
227
+ entity_fields = {**(extra_fields or {})}
228
+ entity_fields.update(
229
+ {
230
+ "entity_id": os_entity_uid,
231
+ "entity_type": entity_type,
232
+ "entity_label": filename,
233
+ "os_item_name": filename,
234
+ "os_parent_folder": os_parent_folder,
235
+ "os_item_type": entity_type,
236
+ }
237
+ )
238
+ if filetype:
239
+ entity_fields["os_item_content_type"] = filetype
240
+
241
+ return await _create_entity_async(
242
+ os_workspace=os_workspace,
243
+ entity_fields=entity_fields,
244
+ file=file,
245
+ content_type=filetype,
246
+ client=client,
247
+ )
248
+
249
+ if not client:
250
+ client = get_default_client()
251
+ if not ontology_name:
252
+ ontology_name = client.ontology
253
+ if ontology_name != client.ontology:
254
+ raise NotImplementedError(
255
+ "This operation is currently only supported on the current ontology!"
256
+ )
257
+ if not os_entity_uid:
258
+ os_entity_uid = str(uuid.uuid4())
259
+ filepath = filename.split("/")[:-1]
260
+ filepath = [elem for elem in filepath if elem]
261
+ workspace_label = await query_ontology.asyncio(
262
+ f"SELECT `entity_label` FROM `timbr`.`os_workspace` WHERE `entity_id`='{os_workspace}'",
263
+ client=client,
264
+ )
265
+ if not workspace_label or not workspace_label[0]:
266
+ raise ValueError("Workspace does not exist!")
267
+ workspace_label = workspace_label[0]["entity_label"]
268
+ if not filepath:
269
+ file_folder = os_workspace
270
+ lookup_base_path = None
271
+ else:
272
+ is_relative = len(filepath) > 0 and filepath[0] == "."
273
+ if is_relative:
274
+ filepath = filepath[1:]
275
+ if os_parent_folder:
276
+ os_parent_workspace = await query_ontology.asyncio(
277
+ f"SELECT `os_workspace` FROM `timbr`.`os_folder` WHERE `entity_id`='{os_parent_folder}'",
278
+ client=client,
279
+ )
280
+ if not os_parent_workspace or not os_parent_workspace[0]:
281
+ raise ValueError("Parent folder does not exist!")
282
+ if os_parent_workspace[0]["os_workspace"] != os_workspace:
283
+ raise ValueError("Parent folder is not in the same workspace!")
284
+ file_folder = os_parent_folder
285
+ lookup_base_path = await get_filepath_from_item.asyncio(
286
+ os_workspace, os_parent_folder, ontology_name, client
287
+ )
288
+ else:
289
+ file_folder = os_workspace
290
+ lookup_base_path = workspace_label
291
+ else:
292
+ if filepath[0] != workspace_label:
293
+ filepath.insert(0, workspace_label)
294
+ filepath = filepath[1:]
295
+ file_folder = os_workspace
296
+ lookup_base_path = workspace_label
297
+ folders_to_write = []
298
+ if lookup_base_path:
299
+ for i in range(len(filepath), 0, -1):
300
+ try:
301
+ lookup_path = lookup_base_path + "/" + "/".join(filepath[:i])
302
+ file_entity = await get_item_from_filepath.asyncio(
303
+ os_workspace, lookup_path, False, ontology_name, client
304
+ )
305
+ if isinstance(file_entity, list):
306
+ file_entity = file_entity[0]
307
+ file_folder = file_entity["os_entity_uid"]
308
+ break
309
+ except ValueError:
310
+ folders_to_write.insert(0, filepath[i - 1])
311
+ if folders_to_write:
312
+ for folder in folders_to_write:
313
+ file_folder = (
314
+ await _write_file_nonrecursive(
315
+ os_workspace,
316
+ file_folder,
317
+ str(uuid.uuid4()),
318
+ folder,
319
+ "os_folder",
320
+ None,
321
+ None,
322
+ client,
323
+ )
324
+ )["os_entity_uid"]
325
+ return await _write_file_nonrecursive(
326
+ os_workspace,
327
+ file_folder,
328
+ os_entity_uid,
329
+ filename.rsplit("/", 1)[-1],
330
+ entity_type,
331
+ filetype,
332
+ file,
333
+ client,
334
+ extra_fields=fields,
335
+ )
@@ -0,0 +1,218 @@
1
+ from typing import Union
2
+ from io import BytesIO
3
+ import logging
4
+ import os
5
+ import httpx
6
+ from urllib import parse as urllib_parse
7
+
8
+ _logger = logging.getLogger(__name__)
9
+
10
+ from ...client import Client, get_default_client
11
+ from ..commons import network_retry_strategy
12
+ from ..exceptions import ApiConnectionError
13
+
14
+
15
+ DEFAULT_TIMEOUT = 120
16
+
17
+
18
+ def _is_dev_mode() -> bool:
19
+ return f"{os.getenv('OS_DEV_MODE')}".lower() == "true"
20
+
21
+
22
+ def _upload_api_url(client: Client, filename: str) -> str:
23
+ """Build the blob upload API endpoint URL."""
24
+ base = client.get_base_url_v1()
25
+ return f"{base}/api/v1/files/upload-blob/{filename}"
26
+
27
+
28
+ def _resolve_url(client: Client, path: str, use_external: bool) -> str:
29
+ """Resolve a presigned URL, joining with base URL in external/dev mode."""
30
+ if use_external:
31
+ return urllib_parse.urljoin(client.get_base_url_v1(), path)
32
+ return path
33
+
34
+
35
+ def _get_upload_info_sync(
36
+ api_url: str, headers: dict, filename: str, client: Client
37
+ ) -> dict:
38
+ """Fetch presigned upload URL and fields from the API."""
39
+ use_external = _is_dev_mode()
40
+ params = {"external_url": use_external}
41
+ response = None
42
+ try:
43
+ for attempt in network_retry_strategy():
44
+ with attempt:
45
+ with httpx.Client(timeout=DEFAULT_TIMEOUT) as http_client:
46
+ response = http_client.get(api_url, headers=headers, params=params)
47
+ response.raise_for_status()
48
+ except Exception:
49
+ raise ApiConnectionError("write_temporary_blob", response, client)
50
+ result = response.json()
51
+ url = result.get("url")
52
+ if not url:
53
+ raise ValueError(f"write_temporary_blob: no upload url returned for {filename}")
54
+ return {
55
+ "url": _resolve_url(client, url, use_external),
56
+ "fields": result.get("fields", {}),
57
+ }
58
+
59
+
60
+ async def _get_upload_info_async(
61
+ api_url: str, headers: dict, filename: str, client: Client
62
+ ) -> dict:
63
+ """Fetch presigned upload URL and fields from the API (async)."""
64
+ use_external = _is_dev_mode()
65
+ params = {"external_url": use_external}
66
+ response = None
67
+ try:
68
+ for attempt in network_retry_strategy():
69
+ with attempt:
70
+ async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT) as http_client:
71
+ response = await http_client.get(
72
+ api_url, headers=headers, params=params
73
+ )
74
+ response.raise_for_status()
75
+ except Exception:
76
+ raise ApiConnectionError("write_temporary_blob", response, client)
77
+ result = response.json()
78
+ url = result.get("url")
79
+ if not url:
80
+ raise ValueError(f"write_temporary_blob: no upload url returned for {filename}")
81
+ return {
82
+ "url": _resolve_url(client, url, use_external),
83
+ "fields": result.get("fields", {}),
84
+ }
85
+
86
+
87
+ def sync(
88
+ filename: str,
89
+ file: Union[str, bytes, BytesIO],
90
+ client: Client = None,
91
+ ):
92
+ """
93
+ # Write a temporary blob to the user's temp bucket
94
+
95
+ Uploads a file to the authenticated user's temporary S3 bucket.
96
+ This is useful for temporary file storage before processing or importing into workspaces.
97
+ The blob is not associated with any workspace entity — use write_file() for that.
98
+
99
+ ## Arguments
100
+ - `filename`: The name for the file in the temp bucket
101
+ - `file`: The file contents — a local file path (str), bytes, or BytesIO
102
+ - `client`: The Client with which to connect to Octostar. If None, the default one is used
103
+
104
+ ## Returns
105
+ None on success
106
+
107
+ ## Raises
108
+ - `ApiConnectionError`: If requesting the presigned upload URL failed
109
+ - `ConnectionError`: If the upload to S3 failed
110
+ """
111
+ if not client:
112
+ client = get_default_client()
113
+
114
+ api_url = _upload_api_url(client, filename)
115
+ auth_headers = dict(client.get_headers())
116
+ upload_info = _get_upload_info_sync(api_url, auth_headers, filename, client)
117
+ upload_url = upload_info["url"]
118
+ upload_fields = upload_info["fields"]
119
+
120
+ should_close = False
121
+ if isinstance(file, str):
122
+ file_obj = open(file, "rb")
123
+ should_close = True
124
+ elif isinstance(file, bytes):
125
+ file_obj = BytesIO(file)
126
+ else:
127
+ file_obj = file
128
+
129
+ try:
130
+ response = None
131
+ try:
132
+ for attempt in network_retry_strategy():
133
+ with attempt:
134
+ if hasattr(file_obj, "seek"):
135
+ file_obj.seek(0)
136
+ with httpx.Client() as http_client:
137
+ response = http_client.post(
138
+ upload_url,
139
+ data=upload_fields,
140
+ files={"file": (filename, file_obj)},
141
+ timeout=DEFAULT_TIMEOUT,
142
+ )
143
+ response.raise_for_status()
144
+ except Exception:
145
+ raise ConnectionError(
146
+ f"write_temporary_blob: upload failed for {filename}"
147
+ + (f" — {response.status_code}: {response.text}" if response else "")
148
+ )
149
+ finally:
150
+ if should_close:
151
+ file_obj.close()
152
+
153
+
154
+ async def asyncio(
155
+ filename: str,
156
+ file: Union[str, bytes, BytesIO],
157
+ client: Client = None,
158
+ ):
159
+ """
160
+ # Write a temporary blob to the user's temp bucket (async)
161
+
162
+ Uploads a file to the authenticated user's temporary S3 bucket.
163
+ This is useful for temporary file storage before processing or importing into workspaces.
164
+ The blob is not associated with any workspace entity — use write_file() for that.
165
+
166
+ ## Arguments
167
+ - `filename`: The name for the file in the temp bucket
168
+ - `file`: The file contents — a local file path (str), bytes, or BytesIO
169
+ - `client`: The Client with which to connect to Octostar. If None, the default one is used
170
+
171
+ ## Returns
172
+ None on success
173
+
174
+ ## Raises
175
+ - `ApiConnectionError`: If requesting the presigned upload URL failed
176
+ - `ConnectionError`: If the upload to S3 failed
177
+ """
178
+ if not client:
179
+ client = get_default_client()
180
+
181
+ api_url = _upload_api_url(client, filename)
182
+ auth_headers = dict(client.get_headers())
183
+ upload_info = await _get_upload_info_async(api_url, auth_headers, filename, client)
184
+ upload_url = upload_info["url"]
185
+ upload_fields = upload_info["fields"]
186
+
187
+ should_close = False
188
+ if isinstance(file, str):
189
+ file_obj = open(file, "rb")
190
+ should_close = True
191
+ elif isinstance(file, bytes):
192
+ file_obj = BytesIO(file)
193
+ else:
194
+ file_obj = file
195
+
196
+ try:
197
+ response = None
198
+ try:
199
+ for attempt in network_retry_strategy():
200
+ with attempt:
201
+ if hasattr(file_obj, "seek"):
202
+ file_obj.seek(0)
203
+ async with httpx.AsyncClient() as http_client:
204
+ response = await http_client.post(
205
+ upload_url,
206
+ data=upload_fields,
207
+ files={"file": (filename, file_obj)},
208
+ timeout=DEFAULT_TIMEOUT,
209
+ )
210
+ response.raise_for_status()
211
+ except Exception:
212
+ raise ConnectionError(
213
+ f"write_temporary_blob: upload failed for {filename}"
214
+ + (f" — {response.status_code}: {response.text}" if response else "")
215
+ )
216
+ finally:
217
+ if should_close:
218
+ file_obj.close()