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,268 @@
|
|
|
1
|
+
from types import SimpleNamespace
|
|
2
|
+
from typing import Dict, TypedDict, Union, List, Any
|
|
3
|
+
import uuid
|
|
4
|
+
import httpx
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
_logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
from ..commons import network_retry_strategy
|
|
10
|
+
from ...client import Client, get_default_client
|
|
11
|
+
from ..exceptions import ApiConnectionError, ApiValidationError
|
|
12
|
+
|
|
13
|
+
MAX_ERROR_DETAILS = 10
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _EntityBaseRequired(TypedDict):
|
|
17
|
+
entity_type: str
|
|
18
|
+
fields: Dict[str, Any]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EntityBase(_EntityBaseRequired, total=False):
|
|
22
|
+
os_entity_uid: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _raise_validation_error(
|
|
26
|
+
message,
|
|
27
|
+
client,
|
|
28
|
+
*,
|
|
29
|
+
entity_errors=None,
|
|
30
|
+
missing_entity_ids=None,
|
|
31
|
+
mismatched_keys=None,
|
|
32
|
+
):
|
|
33
|
+
# status_code=200 mirrors the actual HTTP status — the server accepted
|
|
34
|
+
# the request and returned a structured per-entity breakdown; the
|
|
35
|
+
# failures were inside that response body, not at the transport layer.
|
|
36
|
+
raise ApiValidationError(
|
|
37
|
+
"upsert_entities",
|
|
38
|
+
SimpleNamespace(status_code=200, content=message),
|
|
39
|
+
client,
|
|
40
|
+
entity_errors=entity_errors,
|
|
41
|
+
missing_entity_ids=missing_entity_ids,
|
|
42
|
+
mismatched_keys=mismatched_keys,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _validate_response(body, entities, data, response, client):
|
|
47
|
+
expected_ids = {e["os_entity_uid"] for e in entities}
|
|
48
|
+
returned_ids = {e["entity_id"] for e in data.get("entities", [])}
|
|
49
|
+
errors_list = data.get("errors", [])
|
|
50
|
+
error_ids = {e["entity_id"] for e in errors_list}
|
|
51
|
+
accounted_ids = returned_ids | error_ids
|
|
52
|
+
|
|
53
|
+
missing_ids = expected_ids - accounted_ids
|
|
54
|
+
if missing_ids:
|
|
55
|
+
missing_entities = [e for e in body if e["entity_id"] in missing_ids]
|
|
56
|
+
_raise_validation_error(
|
|
57
|
+
f"Entities missing from response (neither in entities nor errors): "
|
|
58
|
+
f"{missing_ids}, payload: {missing_entities}",
|
|
59
|
+
client,
|
|
60
|
+
missing_entity_ids=missing_ids,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
expected_keys = {(e["entity_id"], e["os_workspace"]) for e in body}
|
|
64
|
+
returned_keys = {
|
|
65
|
+
(e["entity_id"], e["os_workspace"]) for e in data.get("entities", [])
|
|
66
|
+
}
|
|
67
|
+
mismatched = returned_keys - expected_keys
|
|
68
|
+
if mismatched:
|
|
69
|
+
_raise_validation_error(
|
|
70
|
+
f"Returned entities have unexpected (entity_id, os_workspace) keys "
|
|
71
|
+
f"(possibly stale records): {mismatched}",
|
|
72
|
+
client,
|
|
73
|
+
mismatched_keys=list(mismatched),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if errors_list:
|
|
77
|
+
lines = [
|
|
78
|
+
f" {e['entity_id']}: {e['error']}" for e in errors_list[:MAX_ERROR_DETAILS]
|
|
79
|
+
]
|
|
80
|
+
if len(errors_list) > MAX_ERROR_DETAILS:
|
|
81
|
+
lines.append(f" ... and {len(errors_list) - MAX_ERROR_DETAILS} more")
|
|
82
|
+
_raise_validation_error(
|
|
83
|
+
f"{len(errors_list)} entities failed to upsert:\n" + "\n".join(lines),
|
|
84
|
+
client,
|
|
85
|
+
entity_errors=errors_list,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return list({e["entity_id"]: e for e in data.get("entities", [])}.values())
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def sync(
|
|
92
|
+
os_workspace: Union[str, List[str]],
|
|
93
|
+
entities: List[EntityBase],
|
|
94
|
+
ontology_name: str = None,
|
|
95
|
+
client: Client = None,
|
|
96
|
+
):
|
|
97
|
+
"""
|
|
98
|
+
# Create or update a set of local entities in a workspace
|
|
99
|
+
|
|
100
|
+
## Arguments
|
|
101
|
+
- `os_workspace`: The workspace ID, or a list of workspace IDs (one per entity)
|
|
102
|
+
- `entities`: The entities to update or create, as a list of dictionaries
|
|
103
|
+
Each dictionary must contain:
|
|
104
|
+
- `entity_type`: The concept name for the entity
|
|
105
|
+
- `fields`: A dictionary of fields for the entity, according to the concept definition in the ontology
|
|
106
|
+
Each dictionary can optionally contain:
|
|
107
|
+
- `os_entity_uid`: The ID for the entity. Only necessary in case of an update
|
|
108
|
+
- `ontology_name`: The name of the ontology
|
|
109
|
+
- `client`: The Client with which to connect to Octostar. If None, the default one is used
|
|
110
|
+
|
|
111
|
+
## Returns
|
|
112
|
+
The list of created/updated entity records
|
|
113
|
+
|
|
114
|
+
## Raises
|
|
115
|
+
- `NotImplementedError`: If the operation is not supported for the ontology
|
|
116
|
+
- `ApiValidationError`: The server accepted the request but rejected
|
|
117
|
+
one or more entities. The per-entity breakdown is on the
|
|
118
|
+
exception as structured attributes (see the class docstring for
|
|
119
|
+
the shape of each):
|
|
120
|
+
- `entity_errors`: full per-entity error rows from the server
|
|
121
|
+
(``list[{"entity_id": str, "error": str}]``)
|
|
122
|
+
- `missing_entity_ids`: ``set[str]`` of IDs the server returned
|
|
123
|
+
no record for
|
|
124
|
+
- `mismatched_keys`: ``list[(entity_id, os_workspace)]`` of
|
|
125
|
+
entries returned under an unexpected workspace
|
|
126
|
+
- `invalid_fields`: ``dict[entity_id, list[field_name]]`` for
|
|
127
|
+
per-field rejections — drop these fields and re-upsert to
|
|
128
|
+
recover
|
|
129
|
+
- `reserved_keyword_entity_types`: ``set[str]`` of entity-type
|
|
130
|
+
tokens flagged as reserved keywords by the query parser
|
|
131
|
+
Subclass of `ApiConnectionError` for backward compatibility.
|
|
132
|
+
- `ApiConnectionError`: If the transport layer failed (network error,
|
|
133
|
+
non-2xx response from the upsert endpoint).
|
|
134
|
+
"""
|
|
135
|
+
assert len(entities) > 0
|
|
136
|
+
for entity in entities:
|
|
137
|
+
entity["os_entity_uid"] = entity.get("os_entity_uid", None) or str(uuid.uuid4())
|
|
138
|
+
if "fields" not in entity:
|
|
139
|
+
entity["fields"] = dict()
|
|
140
|
+
if isinstance(os_workspace, str):
|
|
141
|
+
os_workspace = [os_workspace] * len(entities)
|
|
142
|
+
assert len(os_workspace) == len(entities)
|
|
143
|
+
if not ontology_name:
|
|
144
|
+
if not client:
|
|
145
|
+
client = get_default_client()
|
|
146
|
+
ontology_name = client.ontology
|
|
147
|
+
if ontology_name != client.ontology:
|
|
148
|
+
raise NotImplementedError(
|
|
149
|
+
"This operation is currently only supported on the current ontology!"
|
|
150
|
+
)
|
|
151
|
+
body = [
|
|
152
|
+
{
|
|
153
|
+
**entities[i]["fields"],
|
|
154
|
+
"entity_id": entities[i]["os_entity_uid"],
|
|
155
|
+
"entity_type": entities[i]["entity_type"],
|
|
156
|
+
"os_workspace": os_workspace[i],
|
|
157
|
+
}
|
|
158
|
+
for i in range(len(entities))
|
|
159
|
+
]
|
|
160
|
+
endpoint_url = f"{client.get_base_url_v1()}/api/v1/entities/upsert"
|
|
161
|
+
headers = {
|
|
162
|
+
"Content-Type": "application/json",
|
|
163
|
+
"Authorization": f"Bearer {client.token}",
|
|
164
|
+
"x-ontology": client.ontology,
|
|
165
|
+
}
|
|
166
|
+
response = None
|
|
167
|
+
try:
|
|
168
|
+
for attempt in network_retry_strategy():
|
|
169
|
+
with attempt:
|
|
170
|
+
with httpx.Client() as httpx_client:
|
|
171
|
+
response = httpx_client.post(
|
|
172
|
+
endpoint_url, json=body, headers=headers, timeout=120
|
|
173
|
+
)
|
|
174
|
+
response.raise_for_status()
|
|
175
|
+
except Exception:
|
|
176
|
+
raise ApiConnectionError("upsert_entities", response, client)
|
|
177
|
+
|
|
178
|
+
return _validate_response(body, entities, response.json(), response, client)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
async def asyncio(
|
|
182
|
+
os_workspace: Union[str, List[str]],
|
|
183
|
+
entities: List[EntityBase],
|
|
184
|
+
ontology_name: str = None,
|
|
185
|
+
client: Client = None,
|
|
186
|
+
):
|
|
187
|
+
"""
|
|
188
|
+
# Create or update asynchronously a set of local entities in a workspace
|
|
189
|
+
|
|
190
|
+
## Arguments
|
|
191
|
+
- `os_workspace`: The workspace ID, or a list of workspace IDs (one per entity)
|
|
192
|
+
- `entities`: The entities to update or create, as a list of dictionaries
|
|
193
|
+
Each dictionary must contain:
|
|
194
|
+
- `entity_type`: The concept name for the entity
|
|
195
|
+
- `fields`: A dictionary of fields for the entity, according to the concept definition in the ontology
|
|
196
|
+
Each dictionary can optionally contain:
|
|
197
|
+
- `os_entity_uid`: The ID for the entity. Only necessary in case of an update
|
|
198
|
+
- `ontology_name`: The name of the ontology
|
|
199
|
+
- `client`: The Client with which to connect to Octostar. If None, the default one is used
|
|
200
|
+
|
|
201
|
+
## Returns
|
|
202
|
+
The list of created/updated entity records
|
|
203
|
+
|
|
204
|
+
## Raises
|
|
205
|
+
- `NotImplementedError`: If the operation is not supported for the ontology
|
|
206
|
+
- `ApiValidationError`: The server accepted the request but rejected
|
|
207
|
+
one or more entities. The per-entity breakdown is on the
|
|
208
|
+
exception as structured attributes (see the class docstring for
|
|
209
|
+
the shape of each):
|
|
210
|
+
- `entity_errors`: full per-entity error rows from the server
|
|
211
|
+
(``list[{"entity_id": str, "error": str}]``)
|
|
212
|
+
- `missing_entity_ids`: ``set[str]`` of IDs the server returned
|
|
213
|
+
no record for
|
|
214
|
+
- `mismatched_keys`: ``list[(entity_id, os_workspace)]`` of
|
|
215
|
+
entries returned under an unexpected workspace
|
|
216
|
+
- `invalid_fields`: ``dict[entity_id, list[field_name]]`` for
|
|
217
|
+
per-field rejections — drop these fields and re-upsert to
|
|
218
|
+
recover
|
|
219
|
+
- `reserved_keyword_entity_types`: ``set[str]`` of entity-type
|
|
220
|
+
tokens flagged as reserved keywords by the query parser
|
|
221
|
+
Subclass of `ApiConnectionError` for backward compatibility.
|
|
222
|
+
- `ApiConnectionError`: If the transport layer failed (network error,
|
|
223
|
+
non-2xx response from the upsert endpoint).
|
|
224
|
+
"""
|
|
225
|
+
assert len(entities) > 0
|
|
226
|
+
for entity in entities:
|
|
227
|
+
entity["os_entity_uid"] = entity.get("os_entity_uid", None) or str(uuid.uuid4())
|
|
228
|
+
if "fields" not in entity:
|
|
229
|
+
entity["fields"] = dict()
|
|
230
|
+
if isinstance(os_workspace, str):
|
|
231
|
+
os_workspace = [os_workspace] * len(entities)
|
|
232
|
+
assert len(os_workspace) == len(entities)
|
|
233
|
+
if not ontology_name:
|
|
234
|
+
if not client:
|
|
235
|
+
client = get_default_client()
|
|
236
|
+
ontology_name = client.ontology
|
|
237
|
+
if ontology_name != client.ontology:
|
|
238
|
+
raise NotImplementedError(
|
|
239
|
+
"This operation is currently only supported on the current ontology!"
|
|
240
|
+
)
|
|
241
|
+
body = [
|
|
242
|
+
{
|
|
243
|
+
**entities[i]["fields"],
|
|
244
|
+
"entity_id": entities[i]["os_entity_uid"],
|
|
245
|
+
"entity_type": entities[i]["entity_type"],
|
|
246
|
+
"os_workspace": os_workspace[i],
|
|
247
|
+
}
|
|
248
|
+
for i in range(len(entities))
|
|
249
|
+
]
|
|
250
|
+
endpoint_url = f"{client.get_base_url_v1()}/api/v1/entities/upsert"
|
|
251
|
+
headers = {
|
|
252
|
+
"Content-Type": "application/json",
|
|
253
|
+
"Authorization": f"Bearer {client.token}",
|
|
254
|
+
"x-ontology": client.ontology,
|
|
255
|
+
}
|
|
256
|
+
response = None
|
|
257
|
+
try:
|
|
258
|
+
for attempt in network_retry_strategy():
|
|
259
|
+
with attempt:
|
|
260
|
+
async with httpx.AsyncClient() as httpx_client:
|
|
261
|
+
response = await httpx_client.post(
|
|
262
|
+
endpoint_url, json=body, headers=headers, timeout=120
|
|
263
|
+
)
|
|
264
|
+
response.raise_for_status()
|
|
265
|
+
except Exception:
|
|
266
|
+
raise ApiConnectionError("upsert_entities", response, client)
|
|
267
|
+
|
|
268
|
+
return _validate_response(body, entities, response.json(), response, client)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from typing import Dict, Union, Any
|
|
2
|
+
import uuid
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from ...client import Client
|
|
6
|
+
from . import upsert_entities
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def sync(
|
|
10
|
+
os_workspace: str,
|
|
11
|
+
os_entity_type: str,
|
|
12
|
+
fields: Dict[str, Any],
|
|
13
|
+
os_entity_uid: Union[str, None] = None,
|
|
14
|
+
ontology_name: str = None,
|
|
15
|
+
client: Client = None,
|
|
16
|
+
):
|
|
17
|
+
"""
|
|
18
|
+
# Create or update a local entity in a workspace
|
|
19
|
+
|
|
20
|
+
## Arguments
|
|
21
|
+
- `os_workspace`: The workspace ID
|
|
22
|
+
- `os_entity_type`: The concept name for the entity
|
|
23
|
+
- `fields`: A dictionary of fields for the entity, according to the concept definition in the ontology
|
|
24
|
+
- `os_entity_uid`: The ID for the entity. Only necessary in case of an update
|
|
25
|
+
- `ontology_name`: The name of the ontology
|
|
26
|
+
- `client`: The Client with which to connect to Octostar. If None, the default one is used
|
|
27
|
+
|
|
28
|
+
## Returns
|
|
29
|
+
The created/updated entity record
|
|
30
|
+
|
|
31
|
+
## Raises
|
|
32
|
+
- `NotImplementedError`: If the operation is not supported for the ontology
|
|
33
|
+
- `ApiValidationError`: The server rejected the entity. The
|
|
34
|
+
breakdown is exposed as structured attributes on the exception
|
|
35
|
+
(see the class docstring for the shape of each):
|
|
36
|
+
- `entity_errors`: ``list[{"entity_id": str, "error": str}]``
|
|
37
|
+
- `invalid_fields`: ``dict[entity_id, list[field_name]]`` —
|
|
38
|
+
drop these fields and re-upsert to recover
|
|
39
|
+
- `reserved_keyword_entity_types`: ``set[str]`` of entity-type
|
|
40
|
+
tokens flagged as reserved keywords
|
|
41
|
+
Subclass of `ApiConnectionError` for backward compatibility.
|
|
42
|
+
- `ApiConnectionError`: If the transport layer failed (network error,
|
|
43
|
+
non-2xx response).
|
|
44
|
+
"""
|
|
45
|
+
result = upsert_entities.sync(
|
|
46
|
+
os_workspace,
|
|
47
|
+
[
|
|
48
|
+
{
|
|
49
|
+
"os_workspace": os_workspace,
|
|
50
|
+
"entity_type": os_entity_type,
|
|
51
|
+
"os_entity_uid": os_entity_uid,
|
|
52
|
+
"fields": fields,
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
ontology_name=ontology_name,
|
|
56
|
+
client=client,
|
|
57
|
+
)
|
|
58
|
+
return result[0]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def asyncio(
|
|
62
|
+
os_workspace: str,
|
|
63
|
+
os_entity_type: str,
|
|
64
|
+
fields: Dict[str, Any],
|
|
65
|
+
os_entity_uid: Union[str, None] = None,
|
|
66
|
+
ontology_name: str = None,
|
|
67
|
+
client: Client = None,
|
|
68
|
+
):
|
|
69
|
+
"""
|
|
70
|
+
# Create or update asynchronously a local entity in a workspace
|
|
71
|
+
|
|
72
|
+
## Arguments
|
|
73
|
+
- `os_workspace`: The workspace ID
|
|
74
|
+
- `os_entity_type`: The concept name for the entity
|
|
75
|
+
- `fields`: A dictionary of fields for the entity, according to the concept definition in the ontology
|
|
76
|
+
- `os_entity_uid`: The ID for the entity. Only necessary in case of an update
|
|
77
|
+
- `ontology_name`: The name of the ontology
|
|
78
|
+
- `client`: The Client with which to connect to Octostar. If None, the default one is used
|
|
79
|
+
|
|
80
|
+
## Returns
|
|
81
|
+
The created/updated entity record
|
|
82
|
+
|
|
83
|
+
## Raises
|
|
84
|
+
- `NotImplementedError`: If the operation is not supported for the ontology
|
|
85
|
+
- `ApiValidationError`: The server rejected the entity. The
|
|
86
|
+
breakdown is exposed as structured attributes on the exception
|
|
87
|
+
(see the class docstring for the shape of each):
|
|
88
|
+
- `entity_errors`: ``list[{"entity_id": str, "error": str}]``
|
|
89
|
+
- `invalid_fields`: ``dict[entity_id, list[field_name]]`` —
|
|
90
|
+
drop these fields and re-upsert to recover
|
|
91
|
+
- `reserved_keyword_entity_types`: ``set[str]`` of entity-type
|
|
92
|
+
tokens flagged as reserved keywords
|
|
93
|
+
Subclass of `ApiConnectionError` for backward compatibility.
|
|
94
|
+
- `ApiConnectionError`: If the transport layer failed (network error,
|
|
95
|
+
non-2xx response).
|
|
96
|
+
"""
|
|
97
|
+
result = await upsert_entities.asyncio(
|
|
98
|
+
os_workspace,
|
|
99
|
+
[
|
|
100
|
+
{
|
|
101
|
+
"os_workspace": os_workspace,
|
|
102
|
+
"entity_type": os_entity_type,
|
|
103
|
+
"os_entity_uid": os_entity_uid,
|
|
104
|
+
"fields": fields,
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
ontology_name=ontology_name,
|
|
108
|
+
client=client,
|
|
109
|
+
)
|
|
110
|
+
return result[0]
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from typing import Dict, Union, Any, Optional
|
|
2
|
+
import uuid
|
|
3
|
+
import os
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
_logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
from ..ontology import query_ontology
|
|
9
|
+
from . import upsert_entities
|
|
10
|
+
from ...types import Unset, UNSET
|
|
11
|
+
from ...client import Client, get_default_client
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def sync(
|
|
15
|
+
os_entity_uid_from: str,
|
|
16
|
+
entity_type_from: str,
|
|
17
|
+
os_entity_uid_to: str,
|
|
18
|
+
entity_type_to: str,
|
|
19
|
+
os_relationship_name: str,
|
|
20
|
+
os_relationship_workspace: str,
|
|
21
|
+
relationship_fields: Dict[str, Any] = None,
|
|
22
|
+
os_relationship_uid: Optional[str] = None,
|
|
23
|
+
allow_multi_cardinality: bool = True,
|
|
24
|
+
ontology_name: str = None,
|
|
25
|
+
client: Client = None,
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
# Create or update a local relationship between two entities, a source and a target
|
|
29
|
+
|
|
30
|
+
## Arguments
|
|
31
|
+
- `os_entity_uid_from`: The source entity ID
|
|
32
|
+
- `entity_type_from`: The source entity concept name
|
|
33
|
+
- `os_entity_uid_to`: The target entity ID
|
|
34
|
+
- `entity_type_to`: The target entity concept name
|
|
35
|
+
- `os_relationship_name`: The name of the relationship, from source to target
|
|
36
|
+
- `os_relationship_workspace`: The workspace for the relationship
|
|
37
|
+
- `relationship_fields`: A dictionary of additional properties for the relationship
|
|
38
|
+
- `os_relationship_uid`: The ID for the relationship. Only necessary in case of an update
|
|
39
|
+
- `allow_multi_cardinality`: If True, allows multiple relationships from the same source, target, and relationship name. Otherwise, it updates them.
|
|
40
|
+
Note this uniqueness is only applied per-workspace
|
|
41
|
+
- `ontology_name`: The name of the ontology
|
|
42
|
+
- `client`: The Client with which to connect to Octostar
|
|
43
|
+
|
|
44
|
+
## Returns
|
|
45
|
+
The created/updated relationship record
|
|
46
|
+
|
|
47
|
+
## Raises
|
|
48
|
+
- `NotImplementedError`: If the operation is not supported for the ontology or for the given data
|
|
49
|
+
- `ApiValidationError`: The server rejected the relationship (the
|
|
50
|
+
underlying ``os_workspace_relationship`` record failed
|
|
51
|
+
validation, the endpoint entity_type is a reserved keyword,
|
|
52
|
+
etc.). The breakdown is exposed as structured attributes (see
|
|
53
|
+
the class docstring for the shape of each):
|
|
54
|
+
- `entity_errors`: ``list[{"entity_id": str, "error": str}]``
|
|
55
|
+
- `invalid_fields`: ``dict[entity_id, list[field_name]]`` —
|
|
56
|
+
drop these fields and re-upsert to recover
|
|
57
|
+
- `reserved_keyword_entity_types`: ``set[str]`` of entity-type
|
|
58
|
+
tokens flagged as reserved keywords
|
|
59
|
+
Subclass of `ApiConnectionError` for backward compatibility.
|
|
60
|
+
- `ApiConnectionError`: If the transport layer failed (network error,
|
|
61
|
+
non-2xx response).
|
|
62
|
+
- `ValueError`: If relationship uniqueness is enforced when the relationship is already not unique
|
|
63
|
+
"""
|
|
64
|
+
if relationship_fields is None:
|
|
65
|
+
relationship_fields = {}
|
|
66
|
+
if not os_relationship_uid:
|
|
67
|
+
os_relationship_uid = str(uuid.uuid4())
|
|
68
|
+
if not client:
|
|
69
|
+
client = get_default_client()
|
|
70
|
+
if not ontology_name:
|
|
71
|
+
ontology_name = client.ontology
|
|
72
|
+
if ontology_name != client.ontology:
|
|
73
|
+
raise NotImplementedError(
|
|
74
|
+
"This operation is currently only supported on the current ontology!"
|
|
75
|
+
)
|
|
76
|
+
if not allow_multi_cardinality:
|
|
77
|
+
check_existence_query = f"""SELECT os_entity_uid FROM dtimbr.os_workspace_relationship
|
|
78
|
+
WHERE os_workspace='{os_relationship_workspace}' AND
|
|
79
|
+
os_relationship_name='{os_relationship_name}' AND
|
|
80
|
+
os_entity_uid_from='{os_entity_uid_from}' AND
|
|
81
|
+
os_entity_type_from='{entity_type_from}' AND
|
|
82
|
+
os_entity_uid_to='{os_entity_uid_to}' AND
|
|
83
|
+
os_entity_type_to='{entity_type_to}' """
|
|
84
|
+
result = query_ontology.sync(check_existence_query, client=client)
|
|
85
|
+
if result:
|
|
86
|
+
if len(result) > 1:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
"Cannot enforce cardinality = 1 when cardinality is already greater than 1!"
|
|
89
|
+
)
|
|
90
|
+
os_relationship_uid = result[0]["os_entity_uid"]
|
|
91
|
+
result = upsert_entities.sync(
|
|
92
|
+
os_relationship_workspace,
|
|
93
|
+
[
|
|
94
|
+
{
|
|
95
|
+
"entity_type": "os_workspace_relationship",
|
|
96
|
+
"os_entity_uid": os_relationship_uid,
|
|
97
|
+
"fields": {
|
|
98
|
+
**relationship_fields,
|
|
99
|
+
"os_entity_uid_from": os_entity_uid_from,
|
|
100
|
+
"os_entity_type_from": entity_type_from,
|
|
101
|
+
"os_entity_uid_to": os_entity_uid_to,
|
|
102
|
+
"os_entity_type_to": entity_type_to,
|
|
103
|
+
"os_relationship_name": os_relationship_name,
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
client=client,
|
|
108
|
+
)
|
|
109
|
+
return result[0]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
async def asyncio(
|
|
113
|
+
os_workspace_from: str,
|
|
114
|
+
os_entity_uid_from: str,
|
|
115
|
+
entity_type_from: str,
|
|
116
|
+
os_workspace_to: Union[None, str],
|
|
117
|
+
os_entity_uid_to: str,
|
|
118
|
+
entity_type_to: str,
|
|
119
|
+
os_relationship_name: str,
|
|
120
|
+
relationship_fields: Dict[str, Any] = None,
|
|
121
|
+
os_relationship_uid: Union[str, Unset] = UNSET,
|
|
122
|
+
ontology_name: str = None,
|
|
123
|
+
client: Client = None,
|
|
124
|
+
):
|
|
125
|
+
"""
|
|
126
|
+
# NOT IMPLEMENTED
|
|
127
|
+
"""
|
|
128
|
+
raise NotImplementedError()
|