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,194 @@
|
|
|
1
|
+
from typing import Dict, Union, List, Any, TypedDict, Optional
|
|
2
|
+
import uuid
|
|
3
|
+
import os
|
|
4
|
+
import itertools
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
_logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
from ..ontology import query_ontology
|
|
10
|
+
from . import upsert_entities
|
|
11
|
+
from ...client import Client, get_default_client
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EntityBase(TypedDict):
|
|
15
|
+
entity_type: str
|
|
16
|
+
os_entity_uid: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def sync(
|
|
20
|
+
entities_from: List[EntityBase],
|
|
21
|
+
entities_to: List[EntityBase],
|
|
22
|
+
os_relationship_names: Union[str, List[str]],
|
|
23
|
+
os_relationship_workspaces: Union[str, List[str]],
|
|
24
|
+
relationship_fields: Optional[List[Dict[str, Any]]] = None,
|
|
25
|
+
os_relationship_uids: Optional[List[Optional[str]]] = None,
|
|
26
|
+
allow_multi_cardinality: bool = True,
|
|
27
|
+
ontology_name: str = None,
|
|
28
|
+
client: Client = None,
|
|
29
|
+
):
|
|
30
|
+
"""
|
|
31
|
+
# Create or update a set of local relationships between pairs of entities (sources and targets)
|
|
32
|
+
|
|
33
|
+
## Arguments
|
|
34
|
+
- `entities_from`: The source entities, as a list of dictionaries
|
|
35
|
+
Each dictionary must contain:
|
|
36
|
+
- `os_workspace`: The workspace ID belonging to the entity. This will also be the workspace ID of
|
|
37
|
+
the corresponding relationship
|
|
38
|
+
- `entity_type`: The concept name for the entity
|
|
39
|
+
- `os_entity_uid`: The ID for the entity. Only necessary in case of an update
|
|
40
|
+
- `entities_to`: The target entities, as a list of dictionaries. These entities are aligned with the
|
|
41
|
+
source entities to create relationships between them. For these entities, os_workspace can be None
|
|
42
|
+
if the target is a global entity
|
|
43
|
+
- `os_relationship_name`: A single string used as the name of all relationships, or a list of relationship names
|
|
44
|
+
- `os_relationship_workspaces`: A single string used as the workspace ID of all relationships, or a list of workspace IDs (one per relationship)
|
|
45
|
+
- `relationship_fields`: A list of dictionaries, each with the additional properties for a relationship
|
|
46
|
+
- `os_relationship_uids`: The list of IDs for the relationships. Only necessary in case of an update
|
|
47
|
+
- `allow_multi_cardinality`: If True, allows multiple relationships from the same source, target, and relationship name. Otherwise, it updates them
|
|
48
|
+
Note this uniqueness is only applied per-workspace
|
|
49
|
+
- `ontology_name`: The name of the ontology
|
|
50
|
+
- `client`: The Client with which to connect to Octostar. If None, the default one is used
|
|
51
|
+
|
|
52
|
+
## Returns
|
|
53
|
+
The list of created/updated relationship records
|
|
54
|
+
|
|
55
|
+
## Raises
|
|
56
|
+
- `AssertionError`: If the length of the input lists do not match or if some source entities
|
|
57
|
+
do not have a workspace ID
|
|
58
|
+
- `NotImplementedError`: If the operation is not supported for the ontology or for the given data
|
|
59
|
+
- `ApiValidationError`: The server rejected one or more
|
|
60
|
+
relationships. The per-entity breakdown is exposed as
|
|
61
|
+
structured attributes on the exception (see the class
|
|
62
|
+
docstring for the shape of each):
|
|
63
|
+
- `entity_errors`: ``list[{"entity_id": str, "error": str}]``
|
|
64
|
+
- `invalid_fields`: ``dict[entity_id, list[field_name]]`` —
|
|
65
|
+
drop these fields and re-upsert to recover
|
|
66
|
+
- `reserved_keyword_entity_types`: ``set[str]`` of entity-type
|
|
67
|
+
tokens flagged as reserved keywords
|
|
68
|
+
Subclass of `ApiConnectionError` for backward compatibility.
|
|
69
|
+
- `ApiConnectionError`: If the transport layer failed (network error,
|
|
70
|
+
non-2xx response).
|
|
71
|
+
- `ValueError`: If relationship uniqueness is enforced when the relationship is already not unique
|
|
72
|
+
"""
|
|
73
|
+
if not client:
|
|
74
|
+
client = get_default_client()
|
|
75
|
+
if not ontology_name:
|
|
76
|
+
ontology_name = client.ontology
|
|
77
|
+
if ontology_name != client.ontology:
|
|
78
|
+
raise NotImplementedError(
|
|
79
|
+
"This operation is currently only supported on the current ontology!"
|
|
80
|
+
)
|
|
81
|
+
assert len(entities_from) > 0
|
|
82
|
+
assert len(entities_from) == len(entities_to)
|
|
83
|
+
if isinstance(os_relationship_workspaces, str):
|
|
84
|
+
os_relationship_workspaces = [
|
|
85
|
+
os_relationship_workspaces for _ in range(len(entities_from))
|
|
86
|
+
]
|
|
87
|
+
assert len(os_relationship_workspaces) == len(entities_from)
|
|
88
|
+
if not os_relationship_uids:
|
|
89
|
+
os_relationship_uids = [str(uuid.uuid4()) for _ in range(len(entities_from))]
|
|
90
|
+
for i in range(len(os_relationship_uids)):
|
|
91
|
+
if not os_relationship_uids[i]:
|
|
92
|
+
os_relationship_uids[i] = str(uuid.uuid4())
|
|
93
|
+
assert len(os_relationship_uids) == len(entities_from)
|
|
94
|
+
if isinstance(os_relationship_names, str):
|
|
95
|
+
os_relationship_names = [
|
|
96
|
+
os_relationship_names for _ in range(len(entities_from))
|
|
97
|
+
]
|
|
98
|
+
assert len(entities_from) == len(os_relationship_names)
|
|
99
|
+
if not relationship_fields:
|
|
100
|
+
relationship_fields = [dict() for _ in range(len(entities_from))]
|
|
101
|
+
for i in range(len(relationship_fields)):
|
|
102
|
+
if not relationship_fields[i]:
|
|
103
|
+
relationship_fields[i] = dict()
|
|
104
|
+
assert len(relationship_fields) == len(entities_from)
|
|
105
|
+
# ensure each relationship exists uniquely, fetch the IDs in that case
|
|
106
|
+
if not allow_multi_cardinality:
|
|
107
|
+
check_existence_table = [
|
|
108
|
+
(
|
|
109
|
+
os_relationship_workspaces[i],
|
|
110
|
+
os_relationship_names[i],
|
|
111
|
+
entities_from[i]["os_entity_uid"],
|
|
112
|
+
entities_from[i]["entity_type"],
|
|
113
|
+
entities_to[i]["os_entity_uid"],
|
|
114
|
+
entities_to[i]["entity_type"],
|
|
115
|
+
)
|
|
116
|
+
for i in range(len(entities_from))
|
|
117
|
+
]
|
|
118
|
+
check_existence_subquery = ""
|
|
119
|
+
for i in range(len(check_existence_table)):
|
|
120
|
+
elem = check_existence_table[i]
|
|
121
|
+
if i == 0:
|
|
122
|
+
check_existence_subquery = f"""SELECT '{i}' AS n,
|
|
123
|
+
'{elem[0]}' AS os_workspace,
|
|
124
|
+
'{elem[1]}' AS os_relationship_name,
|
|
125
|
+
'{elem[2]}' AS os_entity_uid_from,
|
|
126
|
+
'{elem[3]}' AS os_entity_type_from,
|
|
127
|
+
'{elem[4]}' AS os_entity_uid_to,
|
|
128
|
+
'{elem[5]}' AS os_entity_type_to """
|
|
129
|
+
else:
|
|
130
|
+
check_existence_subquery += f"""SELECT '{i}', '{elem[0]}', '{elem[1]}', '{elem[2]}', '{elem[3]}', '{elem[4]}', '{elem[5]}' """
|
|
131
|
+
if i < len(check_existence_table) - 1:
|
|
132
|
+
check_existence_subquery += " UNION ALL "
|
|
133
|
+
check_existence_query = (
|
|
134
|
+
"""SELECT
|
|
135
|
+
os_entity_uid,
|
|
136
|
+
v.n AS n,
|
|
137
|
+
v.os_relationship_name AS os_relationship_name,
|
|
138
|
+
v.os_workspace AS os_workspace,
|
|
139
|
+
v.os_entity_uid_from AS os_entity_uid_from,
|
|
140
|
+
v.os_entity_type_from AS os_entity_type_from,
|
|
141
|
+
v.os_entity_uid_to AS os_entity_uid_to,
|
|
142
|
+
v.os_entity_type_to AS os_entity_type_to
|
|
143
|
+
FROM dtimbr.os_workspace_relationship AS o RIGHT JOIN ("""
|
|
144
|
+
+ check_existence_subquery
|
|
145
|
+
+ """
|
|
146
|
+
) AS v ON
|
|
147
|
+
v.os_workspace = o.os_workspace AND v.os_relationship_name = o.os_relationship_name
|
|
148
|
+
AND v.os_entity_uid_from = o.os_entity_uid_from AND
|
|
149
|
+
v.os_entity_type_from = o.os_entity_type_from AND v.os_entity_uid_to = o.os_entity_uid_to
|
|
150
|
+
AND v.os_entity_type_to = o.os_entity_type_to"""
|
|
151
|
+
)
|
|
152
|
+
result = query_ontology.sync(check_existence_query, client=client)
|
|
153
|
+
result = list(sorted(result, key=lambda x: x["n"]))
|
|
154
|
+
for i, result_i in itertools.groupby(result, key=lambda x: x["n"]):
|
|
155
|
+
result_i = list(result_i)
|
|
156
|
+
if result_i:
|
|
157
|
+
if len(result_i) > 1:
|
|
158
|
+
raise ValueError(
|
|
159
|
+
"Cannot enforce cardinality = 1 when cardinality is already greater than 1!"
|
|
160
|
+
)
|
|
161
|
+
os_relationship_uids[int(i)] = result_i[0]["os_entity_uid"]
|
|
162
|
+
entities = [
|
|
163
|
+
{
|
|
164
|
+
"entity_type": "os_workspace_relationship",
|
|
165
|
+
"os_entity_uid": os_relationship_uids[i],
|
|
166
|
+
"fields": {
|
|
167
|
+
**relationship_fields[i],
|
|
168
|
+
"os_entity_uid_from": entities_from[i]["os_entity_uid"],
|
|
169
|
+
"os_entity_type_from": entities_from[i]["entity_type"],
|
|
170
|
+
"os_entity_uid_to": entities_to[i]["os_entity_uid"],
|
|
171
|
+
"os_entity_type_to": entities_to[i]["entity_type"],
|
|
172
|
+
"os_relationship_name": os_relationship_names[i],
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
for i in range(len(entities_from))
|
|
176
|
+
]
|
|
177
|
+
return upsert_entities.sync(os_relationship_workspaces, entities, client=client)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def asyncio(
|
|
181
|
+
entities_from: List[EntityBase],
|
|
182
|
+
entities_to: List[EntityBase],
|
|
183
|
+
os_relationship_names: Union[str, List[str]],
|
|
184
|
+
os_relationship_workspaces: Union[str, List[str]],
|
|
185
|
+
relationship_fields: Optional[List[Dict[str, Any]]] = None,
|
|
186
|
+
os_relationship_uids: Optional[List[Optional[str]]] = None,
|
|
187
|
+
allow_multi_cardinality: bool = False,
|
|
188
|
+
ontology_name: str = None,
|
|
189
|
+
client: Client = None,
|
|
190
|
+
):
|
|
191
|
+
"""
|
|
192
|
+
# NOT IMPLEMENTED
|
|
193
|
+
"""
|
|
194
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
from typing import Dict, Optional, Union
|
|
2
|
+
from io import BytesIO
|
|
3
|
+
import json
|
|
4
|
+
import uuid
|
|
5
|
+
import logging
|
|
6
|
+
import httpx
|
|
7
|
+
import aiofiles
|
|
8
|
+
from urllib.parse import quote
|
|
9
|
+
|
|
10
|
+
_logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
from ...client import Client, get_default_client
|
|
13
|
+
from ..commons import network_retry_strategy
|
|
14
|
+
from ..exceptions import ApiConnectionError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
DEFAULT_TIMEOUT = 120
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _entity_url(client: Client, os_workspace: str) -> str:
|
|
21
|
+
"""Build the entity creation endpoint URL."""
|
|
22
|
+
base = client.get_base_url_v1()
|
|
23
|
+
return f"{base}/api/v2/entities/{quote(os_workspace, safe='')}"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _create_entity_sync(
|
|
27
|
+
os_workspace: str,
|
|
28
|
+
entity_fields: dict,
|
|
29
|
+
file=None,
|
|
30
|
+
content_type: str = None,
|
|
31
|
+
client: "Client" = None,
|
|
32
|
+
) -> dict:
|
|
33
|
+
"""Create or upsert an entity via the multipart endpoint.
|
|
34
|
+
|
|
35
|
+
Sends a multipart/form-data request with an ``entity`` JSON part
|
|
36
|
+
and an optional ``file`` binary part.
|
|
37
|
+
"""
|
|
38
|
+
url = _entity_url(client, os_workspace)
|
|
39
|
+
headers = dict(client.get_headers())
|
|
40
|
+
|
|
41
|
+
entity_json = json.dumps(entity_fields).encode("utf-8")
|
|
42
|
+
|
|
43
|
+
should_close = False
|
|
44
|
+
file_obj = None
|
|
45
|
+
if file is not None:
|
|
46
|
+
if isinstance(file, str):
|
|
47
|
+
file_obj = open(file, "rb")
|
|
48
|
+
should_close = True
|
|
49
|
+
elif isinstance(file, bytes):
|
|
50
|
+
file_obj = BytesIO(file)
|
|
51
|
+
else:
|
|
52
|
+
file_obj = file
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
response = None
|
|
56
|
+
try:
|
|
57
|
+
for attempt in network_retry_strategy():
|
|
58
|
+
with attempt:
|
|
59
|
+
parts = [("entity", ("entity", entity_json, "application/json"))]
|
|
60
|
+
if file_obj is not None:
|
|
61
|
+
if hasattr(file_obj, "seek"):
|
|
62
|
+
file_obj.seek(0)
|
|
63
|
+
parts.append(
|
|
64
|
+
(
|
|
65
|
+
"file",
|
|
66
|
+
(
|
|
67
|
+
"file",
|
|
68
|
+
file_obj,
|
|
69
|
+
content_type or "application/octet-stream",
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
with httpx.Client(timeout=DEFAULT_TIMEOUT) as http_client:
|
|
74
|
+
response = http_client.post(url, files=parts, headers=headers)
|
|
75
|
+
response.raise_for_status()
|
|
76
|
+
except Exception:
|
|
77
|
+
raise ApiConnectionError("write_attachment", response, client)
|
|
78
|
+
finally:
|
|
79
|
+
if should_close and file_obj:
|
|
80
|
+
file_obj.close()
|
|
81
|
+
|
|
82
|
+
return response.json()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def _create_entity_async(
|
|
86
|
+
os_workspace: str,
|
|
87
|
+
entity_fields: dict,
|
|
88
|
+
file=None,
|
|
89
|
+
content_type: str = None,
|
|
90
|
+
client: "Client" = None,
|
|
91
|
+
) -> dict:
|
|
92
|
+
"""Create or upsert an entity via the multipart endpoint (async).
|
|
93
|
+
|
|
94
|
+
Reads file content into memory for async compatibility, then sends a
|
|
95
|
+
multipart/form-data request with an ``entity`` JSON part and an optional
|
|
96
|
+
``file`` binary part.
|
|
97
|
+
"""
|
|
98
|
+
url = _entity_url(client, os_workspace)
|
|
99
|
+
headers = dict(client.get_headers())
|
|
100
|
+
|
|
101
|
+
entity_json = json.dumps(entity_fields).encode("utf-8")
|
|
102
|
+
|
|
103
|
+
file_content = None
|
|
104
|
+
if file is not None:
|
|
105
|
+
if isinstance(file, str):
|
|
106
|
+
async with aiofiles.open(file, "rb") as f:
|
|
107
|
+
file_content = await f.read()
|
|
108
|
+
elif isinstance(file, bytes):
|
|
109
|
+
file_content = file
|
|
110
|
+
else:
|
|
111
|
+
file_content = file.read() if hasattr(file, "read") else file
|
|
112
|
+
|
|
113
|
+
response = None
|
|
114
|
+
try:
|
|
115
|
+
for attempt in network_retry_strategy():
|
|
116
|
+
with attempt:
|
|
117
|
+
parts = [("entity", ("entity", entity_json, "application/json"))]
|
|
118
|
+
if file_content is not None:
|
|
119
|
+
parts.append(
|
|
120
|
+
(
|
|
121
|
+
"file",
|
|
122
|
+
(
|
|
123
|
+
"file",
|
|
124
|
+
file_content,
|
|
125
|
+
content_type or "application/octet-stream",
|
|
126
|
+
),
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT) as http_client:
|
|
130
|
+
response = await http_client.post(url, files=parts, headers=headers)
|
|
131
|
+
response.raise_for_status()
|
|
132
|
+
except Exception:
|
|
133
|
+
raise ApiConnectionError("write_attachment", response, client)
|
|
134
|
+
|
|
135
|
+
return response.json()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def sync(
|
|
139
|
+
os_workspace: str,
|
|
140
|
+
os_entity_uid: Union[str, None],
|
|
141
|
+
entity_type: str,
|
|
142
|
+
filetype: str,
|
|
143
|
+
file: Union[str, bytes, BytesIO],
|
|
144
|
+
fields: Optional[Dict] = None,
|
|
145
|
+
ontology_name: str = None,
|
|
146
|
+
client: Client = None,
|
|
147
|
+
):
|
|
148
|
+
"""
|
|
149
|
+
# Write an attachment to a workspace for an attachable entity
|
|
150
|
+
|
|
151
|
+
This includes creating or updating the local entity record as well as writing
|
|
152
|
+
the attachment contents to storage so that they can be retrieved with
|
|
153
|
+
read_attachment(). Note that using upsert_entity() only allows to save a record
|
|
154
|
+
about an entity, not the attachment of said entity.
|
|
155
|
+
|
|
156
|
+
## Arguments
|
|
157
|
+
- `os_workspace`: The workspace ID the entity should belong to
|
|
158
|
+
- `os_entity_uid`: The entity ID
|
|
159
|
+
- `entity_type`: The ontological entity type. Ensure this inherits from 'os_attachable'
|
|
160
|
+
- `filetype`: The MIME type for the attachment
|
|
161
|
+
- `file`: The attachment, either an UTF-8 string or a bytes-like
|
|
162
|
+
- `fields`: Optional dict of additional entity fields to include on the entity.
|
|
163
|
+
- `ontology_name`: The name of the ontology
|
|
164
|
+
- `client`: The Client with which to connect to Octostar. If None, the default one is used
|
|
165
|
+
|
|
166
|
+
## Returns
|
|
167
|
+
The record of the written entity
|
|
168
|
+
|
|
169
|
+
## Raises
|
|
170
|
+
- `NotImplementedError`: If the operation is not supported for the ontology
|
|
171
|
+
- `ConnectionError`: If multiple (ambiguous) S3 urls are returned for this file
|
|
172
|
+
- `ApiConnectionError`: If the operation was unsuccessful on the server
|
|
173
|
+
"""
|
|
174
|
+
if not client:
|
|
175
|
+
client = get_default_client()
|
|
176
|
+
if not ontology_name:
|
|
177
|
+
ontology_name = client.ontology
|
|
178
|
+
if ontology_name != client.ontology:
|
|
179
|
+
raise NotImplementedError(
|
|
180
|
+
"This operation is currently only supported on the current ontology!"
|
|
181
|
+
)
|
|
182
|
+
if not os_entity_uid:
|
|
183
|
+
os_entity_uid = str(uuid.uuid4())
|
|
184
|
+
|
|
185
|
+
entity_fields = {**(fields or {})}
|
|
186
|
+
entity_fields.update(
|
|
187
|
+
{
|
|
188
|
+
"entity_id": os_entity_uid,
|
|
189
|
+
"entity_type": entity_type,
|
|
190
|
+
"os_item_content_type": filetype,
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
return _create_entity_sync(
|
|
194
|
+
os_workspace=os_workspace,
|
|
195
|
+
entity_fields=entity_fields,
|
|
196
|
+
file=file,
|
|
197
|
+
content_type=filetype,
|
|
198
|
+
client=client,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
async def asyncio(
|
|
203
|
+
os_workspace: str,
|
|
204
|
+
os_entity_uid: Union[str, None],
|
|
205
|
+
entity_type: str,
|
|
206
|
+
filetype: str,
|
|
207
|
+
file: Union[str, bytes, BytesIO],
|
|
208
|
+
fields: Optional[Dict] = None,
|
|
209
|
+
ontology_name: str = None,
|
|
210
|
+
client: Client = None,
|
|
211
|
+
):
|
|
212
|
+
"""
|
|
213
|
+
# Write an attachment to a workspace for an attachable entity (async)
|
|
214
|
+
|
|
215
|
+
This includes creating or updating the local entity record as well as writing
|
|
216
|
+
the attachment contents to storage so that they can be retrieved with
|
|
217
|
+
read_attachment(). Note that using upsert_entity() only allows to save a record
|
|
218
|
+
about an entity, not the attachment of said entity.
|
|
219
|
+
|
|
220
|
+
## Arguments
|
|
221
|
+
- `os_workspace`: The workspace ID the entity should belong to
|
|
222
|
+
- `os_entity_uid`: The entity ID
|
|
223
|
+
- `entity_type`: The ontological entity type. Ensure this inherits from 'os_attachable'
|
|
224
|
+
- `filetype`: The MIME type for the attachment
|
|
225
|
+
- `file`: The attachment, either an UTF-8 string or a bytes-like
|
|
226
|
+
- `fields`: Optional dict of additional entity fields to include on the entity.
|
|
227
|
+
- `ontology_name`: The name of the ontology
|
|
228
|
+
- `client`: The Client with which to connect to Octostar. If None, the default one is used
|
|
229
|
+
|
|
230
|
+
## Returns
|
|
231
|
+
The record of the written entity
|
|
232
|
+
|
|
233
|
+
## Raises
|
|
234
|
+
- `NotImplementedError`: If the operation is not supported for the ontology
|
|
235
|
+
- `ConnectionError`: If multiple (ambiguous) S3 urls are returned for this file
|
|
236
|
+
- `ApiConnectionError`: If the operation was unsuccessful on the server
|
|
237
|
+
"""
|
|
238
|
+
if not client:
|
|
239
|
+
client = get_default_client()
|
|
240
|
+
if not ontology_name:
|
|
241
|
+
ontology_name = client.ontology
|
|
242
|
+
if ontology_name != client.ontology:
|
|
243
|
+
raise NotImplementedError(
|
|
244
|
+
"This operation is currently only supported on the current ontology!"
|
|
245
|
+
)
|
|
246
|
+
if not os_entity_uid:
|
|
247
|
+
os_entity_uid = str(uuid.uuid4())
|
|
248
|
+
|
|
249
|
+
entity_fields = {**(fields or {})}
|
|
250
|
+
entity_fields.update(
|
|
251
|
+
{
|
|
252
|
+
"entity_id": os_entity_uid,
|
|
253
|
+
"entity_type": entity_type,
|
|
254
|
+
"os_item_content_type": filetype,
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
return await _create_entity_async(
|
|
258
|
+
os_workspace=os_workspace,
|
|
259
|
+
entity_fields=entity_fields,
|
|
260
|
+
file=file,
|
|
261
|
+
content_type=filetype,
|
|
262
|
+
client=client,
|
|
263
|
+
)
|