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.
- octostar/__init__.py +9 -0
- octostar/api/__init__.py +1 -0
- octostar/api/apps/__init__.py +0 -0
- octostar/api/apps/deploy_app.py +210 -0
- octostar/api/apps/execute_app_job.py +188 -0
- octostar/api/apps/get_app_logs.py +210 -0
- octostar/api/apps/get_apps_url.py +188 -0
- octostar/api/apps/get_job_logs.py +210 -0
- octostar/api/apps/get_job_progress.py +162 -0
- octostar/api/apps/kill_job.py +160 -0
- octostar/api/apps/list_app_jobs.py +276 -0
- octostar/api/apps/list_apps.py +251 -0
- octostar/api/apps/set_job_progress.py +216 -0
- octostar/api/apps/undeploy_app.py +160 -0
- octostar/api/metadata/__init__.py +0 -0
- octostar/api/metadata/get_version.py +232 -0
- octostar/api/metadata/get_whoami.py +232 -0
- octostar/api/notifications/__init__.py +0 -0
- octostar/api/notifications/delete_stream.py +222 -0
- octostar/api/notifications/get_subscriptions.py +240 -0
- octostar/api/notifications/publish_notification.py +275 -0
- octostar/api/notifications/pull_events_from_stream.py +282 -0
- octostar/api/notifications/push_event_to_stream.py +265 -0
- octostar/api/notifications/toast.py +264 -0
- octostar/api/ontology/__init__.py +0 -0
- octostar/api/ontology/fetch_ontology_data.py +275 -0
- octostar/api/ontology/get_ontologies.py +237 -0
- octostar/api/ontology/multi_query.py +297 -0
- octostar/api/ontology/query.py +276 -0
- octostar/api/pipeline/__init__.py +1 -0
- octostar/api/pipeline/get_processing_status.py +185 -0
- octostar/api/pipeline/update_processing_status.py +164 -0
- octostar/api/search/__init__.py +0 -0
- octostar/api/search/get_annotations.py +153 -0
- octostar/api/workspace_data/__init__.py +0 -0
- octostar/api/workspace_data/delete_blob.py +212 -0
- octostar/api/workspace_data/delete_entities.py +326 -0
- octostar/api/workspace_data/download_blob.py +235 -0
- octostar/api/workspace_data/get_attachment.py +336 -0
- octostar/api/workspace_data/get_files_tree.py +397 -0
- octostar/api/workspace_data/upload_blob.py +235 -0
- octostar/api/workspace_data/upsert_entities.py +284 -0
- octostar/api/workspace_permissions/__init__.py +0 -0
- octostar/api/workspace_permissions/get_permissions.py +325 -0
- octostar/api/workspace_tags/__init__.py +0 -0
- octostar/api/workspace_tags/delete_tag_from_entities.py +141 -0
- octostar/api/workspace_tags/tag_entities.py +180 -0
- octostar/client.py +492 -0
- octostar/errors.py +50 -0
- octostar/models/__init__.py +249 -0
- octostar/models/acknowledgement.py +74 -0
- octostar/models/acknowledgement_with_data.py +82 -0
- octostar/models/app_status.py +239 -0
- octostar/models/app_status_annotations.py +66 -0
- octostar/models/app_status_labels.py +69 -0
- octostar/models/app_with_url.py +82 -0
- octostar/models/child_processing_status.py +118 -0
- octostar/models/delete_entities_response_401.py +74 -0
- octostar/models/delete_entities_response_409.py +82 -0
- octostar/models/delete_entities_response_500.py +82 -0
- octostar/models/delete_stream_response_401.py +74 -0
- octostar/models/delete_tag_from_entities_response_401.py +74 -0
- octostar/models/deploy_app_json_body.py +90 -0
- octostar/models/deploy_app_json_body_secrets.py +65 -0
- octostar/models/deploy_app_response_200.py +98 -0
- octostar/models/deploy_app_response_200_data.py +60 -0
- octostar/models/deploy_app_response_400.py +82 -0
- octostar/models/deploy_app_response_403.py +82 -0
- octostar/models/deploy_app_response_404.py +82 -0
- octostar/models/deploy_app_response_409.py +82 -0
- octostar/models/deploy_app_response_500.py +82 -0
- octostar/models/entity.py +80 -0
- octostar/models/entity_response.py +99 -0
- octostar/models/entity_response_s3_urls.py +93 -0
- octostar/models/entity_response_s3_urls_additional_property.py +105 -0
- octostar/models/entity_response_s3_urls_additional_property_fields.py +114 -0
- octostar/models/execute_app_job_json_body.py +151 -0
- octostar/models/execute_app_job_json_body_annotation.py +65 -0
- octostar/models/execute_app_job_response_401.py +74 -0
- octostar/models/fetch_ontology_data_response_200.py +60 -0
- octostar/models/fetch_ontology_data_response_401.py +74 -0
- octostar/models/fetch_ontology_data_response_500.py +82 -0
- octostar/models/get_app_logs_response_401.py +74 -0
- octostar/models/get_app_logs_response_404.py +74 -0
- octostar/models/get_app_logs_response_500.py +82 -0
- octostar/models/get_apps_url_json_body.py +76 -0
- octostar/models/get_apps_url_response_401.py +74 -0
- octostar/models/get_apps_url_response_500.py +82 -0
- octostar/models/get_attachment_response_200.py +74 -0
- octostar/models/get_attachment_response_401.py +74 -0
- octostar/models/get_files_tree_response_200.py +106 -0
- octostar/models/get_files_tree_response_200_status.py +8 -0
- octostar/models/get_files_tree_response_400.py +111 -0
- octostar/models/get_files_tree_response_400_data.py +60 -0
- octostar/models/get_files_tree_response_400_status.py +8 -0
- octostar/models/get_files_tree_response_401.py +74 -0
- octostar/models/get_files_tree_response_500.py +111 -0
- octostar/models/get_files_tree_response_500_data.py +60 -0
- octostar/models/get_files_tree_response_500_status.py +8 -0
- octostar/models/get_job_logs_response_401.py +74 -0
- octostar/models/get_job_logs_response_404.py +74 -0
- octostar/models/get_job_logs_response_500.py +82 -0
- octostar/models/get_job_progress_response_401.py +74 -0
- octostar/models/get_object_response_401.py +74 -0
- octostar/models/get_ontologies_response_401.py +74 -0
- octostar/models/get_ontologies_response_500.py +81 -0
- octostar/models/get_permissions_response_200.py +98 -0
- octostar/models/get_permissions_response_400.py +82 -0
- octostar/models/get_permissions_response_401.py +74 -0
- octostar/models/get_permissions_response_500.py +82 -0
- octostar/models/get_processing_status_response_200.py +104 -0
- octostar/models/get_processing_status_response_200_data.py +87 -0
- octostar/models/get_processing_status_response_400.py +82 -0
- octostar/models/get_processing_status_response_500.py +82 -0
- octostar/models/get_subscriptions_response_200_item.py +74 -0
- octostar/models/get_version_response_200.py +74 -0
- octostar/models/get_version_response_404.py +74 -0
- octostar/models/get_whoami_response_200.py +129 -0
- octostar/models/get_whoami_response_401.py +74 -0
- octostar/models/insert_entity.py +114 -0
- octostar/models/insert_entity_base.py +266 -0
- octostar/models/insert_entity_relationships_item.py +107 -0
- octostar/models/insert_entity_request.py +94 -0
- octostar/models/internal_server_error.py +82 -0
- octostar/models/job_execution_result.py +146 -0
- octostar/models/job_status.py +196 -0
- octostar/models/job_status_labels.py +60 -0
- octostar/models/job_with_url.py +82 -0
- octostar/models/kill_job_response_401.py +74 -0
- octostar/models/list_app_jobs_response_401.py +74 -0
- octostar/models/list_app_jobs_response_500.py +82 -0
- octostar/models/list_apps_response_401.py +74 -0
- octostar/models/list_apps_response_500.py +82 -0
- octostar/models/multi_query_json_body.py +100 -0
- octostar/models/multi_query_json_body_queries_item.py +80 -0
- octostar/models/multi_query_response_400.py +82 -0
- octostar/models/multi_query_response_401.py +74 -0
- octostar/models/not_found_error.py +74 -0
- octostar/models/octostar_event.py +96 -0
- octostar/models/octostar_event_octostar_payload.py +100 -0
- octostar/models/octostar_event_octostar_payload_level.py +11 -0
- octostar/models/os_notification.py +122 -0
- octostar/models/processing_status.py +262 -0
- octostar/models/processing_status_code.py +14 -0
- octostar/models/progress_request.py +73 -0
- octostar/models/publish_notification_response_401.py +74 -0
- octostar/models/pull_events_from_stream_response_401.py +74 -0
- octostar/models/push_event_to_stream_response_401.py +74 -0
- octostar/models/query_json_body.py +101 -0
- octostar/models/query_json_body_params.py +60 -0
- octostar/models/query_response_400.py +82 -0
- octostar/models/query_response_401.py +74 -0
- octostar/models/set_job_progress_response_401.py +74 -0
- octostar/models/string_to_value_label_map.py +99 -0
- octostar/models/string_to_value_label_map_data.py +89 -0
- octostar/models/string_to_value_label_map_data_additional_property.py +80 -0
- octostar/models/successful_get_tags.py +103 -0
- octostar/models/successful_insertion.py +98 -0
- octostar/models/tag_entities_response_401.py +74 -0
- octostar/models/toast_level.py +11 -0
- octostar/models/toast_response_401.py +74 -0
- octostar/models/undeploy_app_response_401.py +74 -0
- octostar/models/update_processing_status_response_200.py +82 -0
- octostar/models/update_processing_status_response_400.py +82 -0
- octostar/models/update_processing_status_response_500.py +82 -0
- octostar/models/upsert_entities_response_401.py +74 -0
- octostar/models/upsert_entity.py +114 -0
- octostar/models/upsert_entity_base.py +266 -0
- octostar/models/upsert_entity_relationships_item.py +107 -0
- octostar/py.typed +1 -0
- octostar/types.py +54 -0
- octostar/utils/__init__.py +15 -0
- octostar/utils/chat/__init__.py +0 -0
- octostar/utils/chat/chat.py +513 -0
- octostar/utils/chat/detokenize.py +105 -0
- octostar/utils/chat/get_default_model.py +50 -0
- octostar/utils/chat/list_models.py +91 -0
- octostar/utils/chat/tokenize.py +105 -0
- octostar/utils/commons.py +226 -0
- octostar/utils/exceptions.py +134 -0
- octostar/utils/jobs/__init__.py +0 -0
- octostar/utils/jobs/apps/__init__.py +0 -0
- octostar/utils/jobs/apps/deploy_app.py +81 -0
- octostar/utils/jobs/apps/execute_app_job.py +114 -0
- octostar/utils/jobs/apps/get_app_logs.py +113 -0
- octostar/utils/jobs/apps/get_app_secret.py +102 -0
- octostar/utils/jobs/apps/get_apps_url.py +73 -0
- octostar/utils/jobs/apps/list_app_jobs.py +62 -0
- octostar/utils/jobs/apps/list_apps.py +126 -0
- octostar/utils/jobs/apps/undeploy_app.py +48 -0
- octostar/utils/jobs/get_job_logs.py +113 -0
- octostar/utils/jobs/get_job_progress.py +76 -0
- octostar/utils/jobs/kill_job.py +47 -0
- octostar/utils/jobs/set_job_progress.py +67 -0
- octostar/utils/meta/__init__.py +0 -0
- octostar/utils/meta/get_version.py +30 -0
- octostar/utils/meta/get_whoami.py +30 -0
- octostar/utils/notifications/__init__.py +0 -0
- octostar/utils/notifications/delete_stream.py +58 -0
- octostar/utils/notifications/get_my_subscriptions.py +49 -0
- octostar/utils/notifications/publish_notification.py +73 -0
- octostar/utils/notifications/pull_event_from_stream.py +63 -0
- octostar/utils/notifications/pull_events_from_stream.py +64 -0
- octostar/utils/notifications/push_event_to_stream.py +109 -0
- octostar/utils/notifications/push_events_to_stream.py +137 -0
- octostar/utils/notifications/toast.py +92 -0
- octostar/utils/ontology/__init__.py +10 -0
- octostar/utils/ontology/fetch_ontology_data.py +141 -0
- octostar/utils/ontology/get_ontologies.py +55 -0
- octostar/utils/ontology/multiquery_ontology.py +287 -0
- octostar/utils/ontology/query_ontology.py +186 -0
- octostar/utils/pipeline/__init__.py +1 -0
- octostar/utils/pipeline/get_processing_status.py +230 -0
- octostar/utils/pipeline/update_processing_status.py +286 -0
- octostar/utils/search/__init__.py +11 -0
- octostar/utils/search/bulk_update.py +138 -0
- octostar/utils/search/count.py +117 -0
- octostar/utils/search/get_entity_annotations.py +304 -0
- octostar/utils/search/get_index_definition.py +111 -0
- octostar/utils/search/multi_search.py +129 -0
- octostar/utils/workspace/__init__.py +0 -0
- octostar/utils/workspace/delete_entities.py +247 -0
- octostar/utils/workspace/delete_entity.py +81 -0
- octostar/utils/workspace/delete_relationship.py +78 -0
- octostar/utils/workspace/delete_relationships.py +85 -0
- octostar/utils/workspace/delete_temporary_blob.py +85 -0
- octostar/utils/workspace/extract_entities.py +140 -0
- octostar/utils/workspace/get_filepath_from_item.py +85 -0
- octostar/utils/workspace/get_filepaths_from_items.py +100 -0
- octostar/utils/workspace/get_files_tree.py +102 -0
- octostar/utils/workspace/get_item_from_filepath.py +102 -0
- octostar/utils/workspace/get_items_from_filepaths.py +108 -0
- octostar/utils/workspace/linkcharts/__init__.py +0 -0
- octostar/utils/workspace/linkcharts/create_linkchart.py +241 -0
- octostar/utils/workspace/permissions/PermissionLevel.py +8 -0
- octostar/utils/workspace/permissions/__init__.py +1 -0
- octostar/utils/workspace/permissions/get_permissions.py +81 -0
- octostar/utils/workspace/read_attachment.py +284 -0
- octostar/utils/workspace/read_file.py +113 -0
- octostar/utils/workspace/read_temporary_blob.py +428 -0
- octostar/utils/workspace/saved_searches/__init__.py +0 -0
- octostar/utils/workspace/saved_searches/create_saved_search.py +183 -0
- octostar/utils/workspace/tags/__init__.py +0 -0
- octostar/utils/workspace/tags/delete_tag_from_entities.py +96 -0
- octostar/utils/workspace/tags/tag_entities.py +175 -0
- octostar/utils/workspace/upsert_entities.py +268 -0
- octostar/utils/workspace/upsert_entity.py +110 -0
- octostar/utils/workspace/upsert_relationship.py +128 -0
- octostar/utils/workspace/upsert_relationships.py +194 -0
- octostar/utils/workspace/write_attachment.py +263 -0
- octostar/utils/workspace/write_file.py +335 -0
- octostar/utils/workspace/write_temporary_blob.py +218 -0
- octostar_python_client-0.1.759.dist-info/METADATA +159 -0
- octostar_python_client-0.1.759.dist-info/RECORD +257 -0
- octostar_python_client-0.1.759.dist-info/WHEEL +5 -0
- octostar_python_client-0.1.759.dist-info/licenses/LICENSE +21 -0
- 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()
|