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,241 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import List, Optional, TypedDict
|
|
4
|
+
|
|
5
|
+
from ....client import Client, get_default_client
|
|
6
|
+
from ..write_attachment import _create_entity_async, _create_entity_sync
|
|
7
|
+
|
|
8
|
+
# Linkcharts are stored as os_file entities whose attachment body holds the
|
|
9
|
+
# graph settings JSON; this MIME type tells the linkchart how to read it.
|
|
10
|
+
LINKCHART_CONTENT_TYPE = "application/octostar-linkchart-settings+json"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _normalize_nodes(nodes: List["Node"]) -> List[dict]:
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
**{
|
|
17
|
+
k: v
|
|
18
|
+
for k, v in node.items()
|
|
19
|
+
if k not in ["entity_type", "entity_label", "entity_id"]
|
|
20
|
+
},
|
|
21
|
+
"entity_type": node["entity_type"],
|
|
22
|
+
"entity_label": node["entity_label"],
|
|
23
|
+
"entity_id": node["entity_id"],
|
|
24
|
+
}
|
|
25
|
+
for node in nodes
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _linkchart_v1_content(
|
|
30
|
+
nodes: Optional[List["Node"]], edges: Optional[List["Edge"]]
|
|
31
|
+
) -> str:
|
|
32
|
+
"""Serialize nodes/edges into the LinkChart V1 saved-state the chart reads natively.
|
|
33
|
+
|
|
34
|
+
Nodes become the top-level entity list; each edge resolves its endpoints to those
|
|
35
|
+
entities and carries the label as a relationship. Duplicate nodes and edges are
|
|
36
|
+
collapsed so the graph never receives colliding element ids.
|
|
37
|
+
"""
|
|
38
|
+
normalized = _normalize_nodes(nodes) if nodes else []
|
|
39
|
+
|
|
40
|
+
v1_nodes: List[dict] = []
|
|
41
|
+
nodes_by_id: dict = {}
|
|
42
|
+
# Key by entity_id alone so dedup matches the edge-endpoint lookup below.
|
|
43
|
+
for node in normalized:
|
|
44
|
+
entity_id = node["entity_id"]
|
|
45
|
+
if entity_id in nodes_by_id:
|
|
46
|
+
continue
|
|
47
|
+
nodes_by_id[entity_id] = node
|
|
48
|
+
v1_nodes.append(node)
|
|
49
|
+
|
|
50
|
+
v1_edges: List[dict] = []
|
|
51
|
+
seen_edges = set()
|
|
52
|
+
for edge in edges or []:
|
|
53
|
+
source = nodes_by_id.get(edge["from_node"])
|
|
54
|
+
target = nodes_by_id.get(edge["to_node"])
|
|
55
|
+
if source is None or target is None:
|
|
56
|
+
continue
|
|
57
|
+
relationship_name = edge["label"]
|
|
58
|
+
key = (
|
|
59
|
+
source["entity_type"],
|
|
60
|
+
source["entity_id"],
|
|
61
|
+
relationship_name,
|
|
62
|
+
target["entity_type"],
|
|
63
|
+
target["entity_id"],
|
|
64
|
+
)
|
|
65
|
+
if key in seen_edges:
|
|
66
|
+
continue
|
|
67
|
+
seen_edges.add(key)
|
|
68
|
+
extras = {
|
|
69
|
+
k: v for k, v in edge.items() if k not in ("from_node", "to_node", "label")
|
|
70
|
+
}
|
|
71
|
+
v1_edges.append(
|
|
72
|
+
{
|
|
73
|
+
**extras,
|
|
74
|
+
"from": source,
|
|
75
|
+
"to": target,
|
|
76
|
+
"relationship": {"relationship_name": relationship_name},
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return json.dumps(
|
|
81
|
+
{
|
|
82
|
+
"format": "LinkChart V1",
|
|
83
|
+
"nodes": v1_nodes,
|
|
84
|
+
"edges": v1_edges,
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _linkchart_entity_fields(
|
|
90
|
+
os_workspace: str,
|
|
91
|
+
os_entity_uid: str,
|
|
92
|
+
name: Optional[str],
|
|
93
|
+
path: Optional[str],
|
|
94
|
+
fields: dict,
|
|
95
|
+
) -> dict:
|
|
96
|
+
return {
|
|
97
|
+
**fields,
|
|
98
|
+
"entity_id": os_entity_uid,
|
|
99
|
+
"entity_type": "os_file",
|
|
100
|
+
"entity_label": name,
|
|
101
|
+
"os_item_name": name,
|
|
102
|
+
"os_item_type": "os_file",
|
|
103
|
+
"os_item_content_type": LINKCHART_CONTENT_TYPE,
|
|
104
|
+
"os_parent_folder": os_workspace,
|
|
105
|
+
"#os_path": path,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class _NodeRequired(TypedDict):
|
|
110
|
+
entity_type: str
|
|
111
|
+
entity_label: str
|
|
112
|
+
entity_id: str
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Node(_NodeRequired, total=False):
|
|
116
|
+
"""
|
|
117
|
+
# Represents an Entity in the Linkchart
|
|
118
|
+
|
|
119
|
+
## Attributes:
|
|
120
|
+
- `entity_type` (str): The type of the entity.
|
|
121
|
+
- `entity_label` (str): The label of the entity.
|
|
122
|
+
- `entity_id` (str): The unique identifier for the entity.
|
|
123
|
+
- `**kwargs`: Any additional fields for the entity. Note that if this entity's ID exists in the data,
|
|
124
|
+
the ontology fields will be fetched by the linkchart regardless of this attribute.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
pass # this allows further kwargs
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class _EdgeRequired(TypedDict):
|
|
131
|
+
from_node: str
|
|
132
|
+
to_node: str
|
|
133
|
+
label: str
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class Edge(_EdgeRequired, total=False):
|
|
137
|
+
"""
|
|
138
|
+
# Represents a Relationship between Two Entities in the Linkchart
|
|
139
|
+
|
|
140
|
+
## Attributes:
|
|
141
|
+
- `from_node` (str): The unique identifier (entity_id) of the source node.
|
|
142
|
+
- `to_node` (str): The unique identifier (entity_id) of the target node.
|
|
143
|
+
- `label` (str): The label describing the relationship.
|
|
144
|
+
- `**kwargs`: Any additional fields for the relationship. Note that if this relationship's ID exists in the data,
|
|
145
|
+
the ontology fields will be fetched by the linkchart regardless of this attribute.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
pass # this allows further kwargs
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def sync(
|
|
152
|
+
os_workspace: str,
|
|
153
|
+
name: Optional[str] = None,
|
|
154
|
+
nodes: Optional[List[Node]] = None,
|
|
155
|
+
edges: Optional[List[Edge]] = None,
|
|
156
|
+
path: Optional[str] = None,
|
|
157
|
+
os_entity_uid: Optional[str] = None,
|
|
158
|
+
fields: dict = {},
|
|
159
|
+
client: Optional[Client] = None,
|
|
160
|
+
):
|
|
161
|
+
"""
|
|
162
|
+
# Create or update a local linkchart in a workspace
|
|
163
|
+
|
|
164
|
+
## Arguments
|
|
165
|
+
- `os_workspace`: The workspace ID.
|
|
166
|
+
- `name`: The name for the linkchart.
|
|
167
|
+
- `nodes`: A list of entities to add to the linkchart.
|
|
168
|
+
- `edges`: A list of edges to add to the linkchart.
|
|
169
|
+
- `path`: The path to the linkchart file in the workspace, excluding the linkchart filename.
|
|
170
|
+
- `os_entity_uid`: The entity ID for the linkchart. Only necessary in case of an update to the linkchart.
|
|
171
|
+
- `fields`: Additional fields to include in the entity record.
|
|
172
|
+
- `client`: The Client with which to connect to Octostar. If None, the default one is used.
|
|
173
|
+
|
|
174
|
+
## Returns
|
|
175
|
+
The created/updated entity record.
|
|
176
|
+
|
|
177
|
+
## Raises
|
|
178
|
+
- `NotImplementedError`: If the operation is not supported for the ontology.
|
|
179
|
+
- `ConnectionError`: If the operation was unsuccessful on the server.
|
|
180
|
+
"""
|
|
181
|
+
if not client:
|
|
182
|
+
client = get_default_client()
|
|
183
|
+
if not os_entity_uid:
|
|
184
|
+
os_entity_uid = str(uuid.uuid4())
|
|
185
|
+
|
|
186
|
+
content = _linkchart_v1_content(nodes, edges)
|
|
187
|
+
return _create_entity_sync(
|
|
188
|
+
os_workspace=os_workspace,
|
|
189
|
+
entity_fields=_linkchart_entity_fields(
|
|
190
|
+
os_workspace, os_entity_uid, name, path, fields
|
|
191
|
+
),
|
|
192
|
+
file=content.encode("utf-8"),
|
|
193
|
+
content_type=LINKCHART_CONTENT_TYPE,
|
|
194
|
+
client=client,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
async def asyncio(
|
|
199
|
+
os_workspace: str,
|
|
200
|
+
name: Optional[str] = None,
|
|
201
|
+
nodes: Optional[List[Node]] = None,
|
|
202
|
+
edges: Optional[List[Edge]] = None,
|
|
203
|
+
path: Optional[str] = None,
|
|
204
|
+
os_entity_uid: Optional[str] = None,
|
|
205
|
+
fields: dict = {},
|
|
206
|
+
client: Optional[Client] = None,
|
|
207
|
+
):
|
|
208
|
+
"""
|
|
209
|
+
Create or update asynchronously a local linkchart in a workspace.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
os_workspace: The workspace ID.
|
|
213
|
+
name: The name for the linkchart.
|
|
214
|
+
nodes: A list of entities to add to the linkchart.
|
|
215
|
+
edges: A list of edges to add to the linkchart.
|
|
216
|
+
path: The path to the linkchart file in the workspace, excluding the linkchart filename.
|
|
217
|
+
os_entity_uid: The entity ID for the linkchart. Only necessary in case of an update to the linkchart.
|
|
218
|
+
fields: Additional fields to include in the entity record.
|
|
219
|
+
client: The Client with which to connect to Octostar. If None, the default one is used.
|
|
220
|
+
Returns:
|
|
221
|
+
The created/updated entity record.
|
|
222
|
+
|
|
223
|
+
Raises:
|
|
224
|
+
NotImplementedError: If the operation is not supported for the ontology.
|
|
225
|
+
ConnectionError: If the operation was unsuccessful on the server.
|
|
226
|
+
"""
|
|
227
|
+
if not client:
|
|
228
|
+
client = get_default_client()
|
|
229
|
+
if not os_entity_uid:
|
|
230
|
+
os_entity_uid = str(uuid.uuid4())
|
|
231
|
+
|
|
232
|
+
content = _linkchart_v1_content(nodes, edges)
|
|
233
|
+
return await _create_entity_async(
|
|
234
|
+
os_workspace=os_workspace,
|
|
235
|
+
entity_fields=_linkchart_entity_fields(
|
|
236
|
+
os_workspace, os_entity_uid, name, path, fields
|
|
237
|
+
),
|
|
238
|
+
file=content.encode("utf-8"),
|
|
239
|
+
content_type=LINKCHART_CONTENT_TYPE,
|
|
240
|
+
client=client,
|
|
241
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .PermissionLevel import PermissionLevel
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import List, Union
|
|
3
|
+
|
|
4
|
+
logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
from .PermissionLevel import PermissionLevel
|
|
7
|
+
from ....api.workspace_permissions import get_permissions
|
|
8
|
+
from ....client import Client
|
|
9
|
+
from ...exceptions import ApiConnectionError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _parse_permissions_response(response, client):
|
|
13
|
+
"""Parse the permissions response."""
|
|
14
|
+
if response.status_code != 200:
|
|
15
|
+
raise ApiConnectionError("get_permissions", response, client)
|
|
16
|
+
|
|
17
|
+
parsed = response.parsed
|
|
18
|
+
if parsed is None:
|
|
19
|
+
raise ApiConnectionError("get_permissions", response, client)
|
|
20
|
+
|
|
21
|
+
permissions_list = getattr(parsed, "additional_properties", {}).get("permissions")
|
|
22
|
+
if permissions_list is None:
|
|
23
|
+
raise ApiConnectionError("get_permissions", response, client)
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
p["workspace"]: PermissionLevel(p["permission_level"])
|
|
27
|
+
for p in permissions_list
|
|
28
|
+
if "workspace" in p and "permission_level" in p
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def sync(os_workspace: Union[List[str], str], client: Client = None):
|
|
33
|
+
"""
|
|
34
|
+
# Get permissions for workspaces
|
|
35
|
+
|
|
36
|
+
Get the permissions for one or more workspaces.
|
|
37
|
+
|
|
38
|
+
## Arguments
|
|
39
|
+
- `os_workspace`: The ID (or list of IDs) of the workspaces to find permissions for.
|
|
40
|
+
- `client`: The Client with which to connect to Octostar, which is also the user for
|
|
41
|
+
which the permissions are given. If None, the default one is used.
|
|
42
|
+
|
|
43
|
+
## Returns
|
|
44
|
+
A dictionary, where each key is a workspace ID and each value is the permission
|
|
45
|
+
level for that workspace.
|
|
46
|
+
|
|
47
|
+
## Raises
|
|
48
|
+
- `ApiConnectionError`: If the request was unsuccessful on the server.
|
|
49
|
+
"""
|
|
50
|
+
if not isinstance(os_workspace, list):
|
|
51
|
+
os_workspace = [os_workspace]
|
|
52
|
+
response = get_permissions.sync_detailed(
|
|
53
|
+
os_workspaces=",".join(os_workspace), client=client
|
|
54
|
+
)
|
|
55
|
+
return _parse_permissions_response(response, client)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def asyncio(os_workspace: Union[List[str], str], client: Client = None):
|
|
59
|
+
"""
|
|
60
|
+
# Get permissions for workspaces asynchronously
|
|
61
|
+
|
|
62
|
+
Get asynchronously the permissions for one or more workspaces.
|
|
63
|
+
|
|
64
|
+
## Arguments
|
|
65
|
+
- `os_workspace`: The ID (or list of IDs) of the workspaces to find permissions for.
|
|
66
|
+
- `client`: The Client with which to connect to Octostar, which is also the user for
|
|
67
|
+
which the permissions are given. If None, the default one is used.
|
|
68
|
+
|
|
69
|
+
## Returns
|
|
70
|
+
A dictionary, where each key is a workspace ID and each value is the permission
|
|
71
|
+
level for that workspace.
|
|
72
|
+
|
|
73
|
+
## Raises
|
|
74
|
+
- `ApiConnectionError`: If the request was unsuccessful on the server.
|
|
75
|
+
"""
|
|
76
|
+
if not isinstance(os_workspace, list):
|
|
77
|
+
os_workspace = [os_workspace]
|
|
78
|
+
response = await get_permissions.asyncio_detailed(
|
|
79
|
+
os_workspaces=",".join(os_workspace), client=client
|
|
80
|
+
)
|
|
81
|
+
return _parse_permissions_response(response, client)
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import codecs
|
|
3
|
+
import httpx
|
|
4
|
+
from typing import Dict, Optional, Tuple
|
|
5
|
+
from urllib.parse import quote
|
|
6
|
+
|
|
7
|
+
_logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
from ...client import Client, get_default_client
|
|
10
|
+
from ..commons import network_retry_strategy
|
|
11
|
+
from ..exceptions import ApiConnectionError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
DEFAULT_TIMEOUT = 120
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _attachment_url(client: Client, os_workspace: str, os_entity_uid: str) -> str:
|
|
18
|
+
"""Build the attachment endpoint URL."""
|
|
19
|
+
base = client.get_base_url_v1()
|
|
20
|
+
return f"{base}/api/v2/entities/{quote(os_workspace, safe='')}/{quote(os_entity_uid, safe='')}/attachment"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _build_headers(
|
|
24
|
+
client: Client,
|
|
25
|
+
byte_range: Optional[Tuple[int, Optional[int]]] = None,
|
|
26
|
+
) -> dict:
|
|
27
|
+
"""Build request headers including auth and optional Range."""
|
|
28
|
+
headers = dict(client.get_headers())
|
|
29
|
+
if byte_range is not None:
|
|
30
|
+
start, end = byte_range
|
|
31
|
+
if end is not None:
|
|
32
|
+
headers["Range"] = f"bytes={start}-{end}"
|
|
33
|
+
else:
|
|
34
|
+
headers["Range"] = f"bytes={start}-"
|
|
35
|
+
return headers
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _head_sync(url: str, headers: dict, client: Client) -> Dict[str, str]:
|
|
39
|
+
"""Send a HEAD request and return the response headers as a dict."""
|
|
40
|
+
response = None
|
|
41
|
+
try:
|
|
42
|
+
for attempt in network_retry_strategy():
|
|
43
|
+
with attempt:
|
|
44
|
+
with httpx.Client(timeout=DEFAULT_TIMEOUT) as http_client:
|
|
45
|
+
response = http_client.head(url, headers=headers)
|
|
46
|
+
response.raise_for_status()
|
|
47
|
+
except Exception:
|
|
48
|
+
raise ApiConnectionError("read_attachment", response, client)
|
|
49
|
+
return dict(response.headers)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def _head_async(url: str, headers: dict, client: Client) -> Dict[str, str]:
|
|
53
|
+
"""Send a HEAD request and return the response headers as a dict (async)."""
|
|
54
|
+
response = None
|
|
55
|
+
try:
|
|
56
|
+
for attempt in network_retry_strategy():
|
|
57
|
+
with attempt:
|
|
58
|
+
async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT) as http_client:
|
|
59
|
+
response = await http_client.head(url, headers=headers)
|
|
60
|
+
response.raise_for_status()
|
|
61
|
+
except Exception:
|
|
62
|
+
raise ApiConnectionError("read_attachment", response, client)
|
|
63
|
+
return dict(response.headers)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _decode_iter(chunks, encoding="utf-8", errors="strict"):
|
|
67
|
+
decoder = codecs.getincrementaldecoder(encoding)(errors=errors)
|
|
68
|
+
for chunk in chunks:
|
|
69
|
+
text = decoder.decode(chunk)
|
|
70
|
+
if text or not chunk:
|
|
71
|
+
yield text
|
|
72
|
+
tail = decoder.decode(b"", final=True)
|
|
73
|
+
if tail:
|
|
74
|
+
yield tail
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def _decode_iter_async(chunks, encoding="utf-8", errors="strict"):
|
|
78
|
+
decoder = codecs.getincrementaldecoder(encoding)(errors=errors)
|
|
79
|
+
async for chunk in chunks:
|
|
80
|
+
text = decoder.decode(chunk)
|
|
81
|
+
if text or not chunk:
|
|
82
|
+
yield text
|
|
83
|
+
tail = decoder.decode(b"", final=True)
|
|
84
|
+
if tail:
|
|
85
|
+
yield tail
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _iter_lines(chunks_iter):
|
|
89
|
+
pending = b""
|
|
90
|
+
for chunk in chunks_iter:
|
|
91
|
+
data = pending + chunk
|
|
92
|
+
pending = b""
|
|
93
|
+
lines = data.splitlines(keepends=True)
|
|
94
|
+
if not lines:
|
|
95
|
+
continue
|
|
96
|
+
if not lines[-1].endswith(b"\n"):
|
|
97
|
+
pending = lines.pop()
|
|
98
|
+
for line in lines:
|
|
99
|
+
yield line.rstrip(b"\r\n")
|
|
100
|
+
if pending:
|
|
101
|
+
yield pending.rstrip(b"\r\n")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
async def _iter_lines_async(chunks_iter):
|
|
105
|
+
pending = b""
|
|
106
|
+
async for chunk in chunks_iter:
|
|
107
|
+
data = pending + chunk
|
|
108
|
+
pending = b""
|
|
109
|
+
lines = data.splitlines(keepends=True)
|
|
110
|
+
if not lines:
|
|
111
|
+
continue
|
|
112
|
+
if not lines[-1].endswith(b"\n"):
|
|
113
|
+
pending = lines.pop()
|
|
114
|
+
for line in lines:
|
|
115
|
+
yield line.rstrip(b"\r\n")
|
|
116
|
+
if pending:
|
|
117
|
+
yield pending.rstrip(b"\r\n")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def sync(
|
|
121
|
+
os_workspace: str,
|
|
122
|
+
os_entity_uid: str,
|
|
123
|
+
decode: bool = True,
|
|
124
|
+
stream: bool = False,
|
|
125
|
+
stream_lines: bool = False,
|
|
126
|
+
stream_chunk_size: int = 65_536,
|
|
127
|
+
byte_range: Optional[Tuple[int, Optional[int]]] = None,
|
|
128
|
+
headers_only: bool = False,
|
|
129
|
+
client: Client = None,
|
|
130
|
+
):
|
|
131
|
+
"""
|
|
132
|
+
# Read the content of an attachment from its workspace ID and object ID
|
|
133
|
+
|
|
134
|
+
Note that querying the ontology for the same entry will only provide metadata
|
|
135
|
+
information about the entity, not the attachment contents themselves.
|
|
136
|
+
|
|
137
|
+
## Arguments
|
|
138
|
+
- `os_workspace`: The workspace ID the object belongs to
|
|
139
|
+
- `os_entity_uid`: The object ID
|
|
140
|
+
- `decode`: Whether to decode the contents to UTF-8
|
|
141
|
+
- `stream`: Whether to return the contents in chunks. Overrides stream_lines and stream_chunk_size if False
|
|
142
|
+
- `stream_lines`: Whether to chunk the contents per line. Overrides stream_chunk_size if set
|
|
143
|
+
- `stream_chunk_size`: How many bytes each chunk should be
|
|
144
|
+
- `byte_range`: Optional (start, end) tuple for Range requests. end can be None for open-ended ranges.
|
|
145
|
+
Example: (0, 1023) fetches the first 1024 bytes; (1024, None) fetches from byte 1024 onwards.
|
|
146
|
+
- `headers_only`: If True, send a HEAD request and return the response headers as a dict
|
|
147
|
+
instead of downloading the body. Useful for inspecting content-type, content-length,
|
|
148
|
+
etag, or last-modified without fetching the full attachment.
|
|
149
|
+
- `client`: The Client with which to connect to Octostar. If None, the default one is used
|
|
150
|
+
|
|
151
|
+
## Returns
|
|
152
|
+
- When `headers_only=True`: a ``dict[str, str]`` of response headers.
|
|
153
|
+
- Otherwise: a string or bytes representation of the object file contents.
|
|
154
|
+
|
|
155
|
+
## Raises
|
|
156
|
+
- `ApiConnectionError`: If the operation was unsuccessful on the server
|
|
157
|
+
- `ValueError`: If the object has no associated file
|
|
158
|
+
"""
|
|
159
|
+
if not client:
|
|
160
|
+
client = get_default_client()
|
|
161
|
+
|
|
162
|
+
url = _attachment_url(client, os_workspace, os_entity_uid)
|
|
163
|
+
headers = _build_headers(client, byte_range)
|
|
164
|
+
|
|
165
|
+
if headers_only:
|
|
166
|
+
return _head_sync(url, headers, client)
|
|
167
|
+
|
|
168
|
+
if not stream:
|
|
169
|
+
response = None
|
|
170
|
+
try:
|
|
171
|
+
for attempt in network_retry_strategy():
|
|
172
|
+
with attempt:
|
|
173
|
+
with httpx.Client(timeout=DEFAULT_TIMEOUT) as http_client:
|
|
174
|
+
response = http_client.get(url, headers=headers)
|
|
175
|
+
response.raise_for_status()
|
|
176
|
+
except Exception:
|
|
177
|
+
raise ApiConnectionError("read_attachment", response, client)
|
|
178
|
+
attachment = response.content
|
|
179
|
+
if decode:
|
|
180
|
+
attachment = attachment.decode()
|
|
181
|
+
return attachment
|
|
182
|
+
|
|
183
|
+
# Streaming mode — return a generator that keeps the connection alive
|
|
184
|
+
def _stream_chunks():
|
|
185
|
+
with httpx.Client(timeout=DEFAULT_TIMEOUT) as http_client:
|
|
186
|
+
with http_client.stream("GET", url, headers=headers) as response:
|
|
187
|
+
if response.status_code >= 400:
|
|
188
|
+
raise ApiConnectionError("read_attachment", response, client)
|
|
189
|
+
for chunk in response.iter_bytes(chunk_size=stream_chunk_size):
|
|
190
|
+
yield chunk
|
|
191
|
+
|
|
192
|
+
if stream_lines:
|
|
193
|
+
result = _iter_lines(_stream_chunks())
|
|
194
|
+
else:
|
|
195
|
+
result = _stream_chunks()
|
|
196
|
+
|
|
197
|
+
if decode:
|
|
198
|
+
result = _decode_iter(result)
|
|
199
|
+
return result
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
async def asyncio(
|
|
203
|
+
os_workspace: str,
|
|
204
|
+
os_entity_uid: str,
|
|
205
|
+
decode: bool = True,
|
|
206
|
+
stream: bool = False,
|
|
207
|
+
stream_lines: bool = False,
|
|
208
|
+
stream_chunk_size: int = 65_536,
|
|
209
|
+
byte_range: Optional[Tuple[int, Optional[int]]] = None,
|
|
210
|
+
headers_only: bool = False,
|
|
211
|
+
client: Client = None,
|
|
212
|
+
):
|
|
213
|
+
"""
|
|
214
|
+
# Read the content of an attachment from its workspace ID and object ID (async)
|
|
215
|
+
|
|
216
|
+
Note that querying the ontology for the same entry will only provide metadata
|
|
217
|
+
information about the entity, not the attachment contents themselves.
|
|
218
|
+
|
|
219
|
+
## Arguments
|
|
220
|
+
- `os_workspace`: The workspace ID the object belongs to
|
|
221
|
+
- `os_entity_uid`: The object ID
|
|
222
|
+
- `decode`: Whether to decode the contents to UTF-8
|
|
223
|
+
- `stream`: Whether to return the contents in chunks. Overrides stream_lines and stream_chunk_size if False
|
|
224
|
+
- `stream_lines`: Whether to chunk the contents per line. Overrides stream_chunk_size if set
|
|
225
|
+
- `stream_chunk_size`: How many bytes each chunk should be
|
|
226
|
+
- `byte_range`: Optional (start, end) tuple for Range requests. end can be None for open-ended ranges.
|
|
227
|
+
Example: (0, 1023) fetches the first 1024 bytes; (1024, None) fetches from byte 1024 onwards.
|
|
228
|
+
- `headers_only`: If True, send a HEAD request and return the response headers as a dict
|
|
229
|
+
instead of downloading the body. Useful for inspecting content-type, content-length,
|
|
230
|
+
etag, or last-modified without fetching the full attachment.
|
|
231
|
+
- `client`: The Client with which to connect to Octostar. If None, the default one is used
|
|
232
|
+
|
|
233
|
+
## Returns
|
|
234
|
+
- When `headers_only=True`: a ``dict[str, str]`` of response headers.
|
|
235
|
+
- Otherwise: a string or bytes representation of the object file contents,
|
|
236
|
+
or an async generator when streaming.
|
|
237
|
+
|
|
238
|
+
## Raises
|
|
239
|
+
- `ApiConnectionError`: If the operation was unsuccessful on the server
|
|
240
|
+
- `ValueError`: If the object has no associated file
|
|
241
|
+
"""
|
|
242
|
+
if not client:
|
|
243
|
+
client = get_default_client()
|
|
244
|
+
|
|
245
|
+
url = _attachment_url(client, os_workspace, os_entity_uid)
|
|
246
|
+
headers = _build_headers(client, byte_range)
|
|
247
|
+
|
|
248
|
+
if headers_only:
|
|
249
|
+
return await _head_async(url, headers, client)
|
|
250
|
+
|
|
251
|
+
if not stream:
|
|
252
|
+
response = None
|
|
253
|
+
try:
|
|
254
|
+
for attempt in network_retry_strategy():
|
|
255
|
+
with attempt:
|
|
256
|
+
async with httpx.AsyncClient(
|
|
257
|
+
timeout=DEFAULT_TIMEOUT
|
|
258
|
+
) as http_client:
|
|
259
|
+
response = await http_client.get(url, headers=headers)
|
|
260
|
+
response.raise_for_status()
|
|
261
|
+
except Exception:
|
|
262
|
+
raise ApiConnectionError("read_attachment", response, client)
|
|
263
|
+
attachment = response.content
|
|
264
|
+
if decode:
|
|
265
|
+
attachment = attachment.decode()
|
|
266
|
+
return attachment
|
|
267
|
+
|
|
268
|
+
# Streaming mode — return an async generator
|
|
269
|
+
async def _stream_chunks():
|
|
270
|
+
async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT) as http_client:
|
|
271
|
+
async with http_client.stream("GET", url, headers=headers) as response:
|
|
272
|
+
if response.status_code >= 400:
|
|
273
|
+
raise ApiConnectionError("read_attachment", response, client)
|
|
274
|
+
async for chunk in response.aiter_bytes(chunk_size=stream_chunk_size):
|
|
275
|
+
yield chunk
|
|
276
|
+
|
|
277
|
+
if stream_lines:
|
|
278
|
+
result = _iter_lines_async(_stream_chunks())
|
|
279
|
+
else:
|
|
280
|
+
result = _stream_chunks()
|
|
281
|
+
|
|
282
|
+
if decode:
|
|
283
|
+
result = _decode_iter_async(result)
|
|
284
|
+
return result
|