octostar-python-client 0.1.759__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. octostar/__init__.py +9 -0
  2. octostar/api/__init__.py +1 -0
  3. octostar/api/apps/__init__.py +0 -0
  4. octostar/api/apps/deploy_app.py +210 -0
  5. octostar/api/apps/execute_app_job.py +188 -0
  6. octostar/api/apps/get_app_logs.py +210 -0
  7. octostar/api/apps/get_apps_url.py +188 -0
  8. octostar/api/apps/get_job_logs.py +210 -0
  9. octostar/api/apps/get_job_progress.py +162 -0
  10. octostar/api/apps/kill_job.py +160 -0
  11. octostar/api/apps/list_app_jobs.py +276 -0
  12. octostar/api/apps/list_apps.py +251 -0
  13. octostar/api/apps/set_job_progress.py +216 -0
  14. octostar/api/apps/undeploy_app.py +160 -0
  15. octostar/api/metadata/__init__.py +0 -0
  16. octostar/api/metadata/get_version.py +232 -0
  17. octostar/api/metadata/get_whoami.py +232 -0
  18. octostar/api/notifications/__init__.py +0 -0
  19. octostar/api/notifications/delete_stream.py +222 -0
  20. octostar/api/notifications/get_subscriptions.py +240 -0
  21. octostar/api/notifications/publish_notification.py +275 -0
  22. octostar/api/notifications/pull_events_from_stream.py +282 -0
  23. octostar/api/notifications/push_event_to_stream.py +265 -0
  24. octostar/api/notifications/toast.py +264 -0
  25. octostar/api/ontology/__init__.py +0 -0
  26. octostar/api/ontology/fetch_ontology_data.py +275 -0
  27. octostar/api/ontology/get_ontologies.py +237 -0
  28. octostar/api/ontology/multi_query.py +297 -0
  29. octostar/api/ontology/query.py +276 -0
  30. octostar/api/pipeline/__init__.py +1 -0
  31. octostar/api/pipeline/get_processing_status.py +185 -0
  32. octostar/api/pipeline/update_processing_status.py +164 -0
  33. octostar/api/search/__init__.py +0 -0
  34. octostar/api/search/get_annotations.py +153 -0
  35. octostar/api/workspace_data/__init__.py +0 -0
  36. octostar/api/workspace_data/delete_blob.py +212 -0
  37. octostar/api/workspace_data/delete_entities.py +326 -0
  38. octostar/api/workspace_data/download_blob.py +235 -0
  39. octostar/api/workspace_data/get_attachment.py +336 -0
  40. octostar/api/workspace_data/get_files_tree.py +397 -0
  41. octostar/api/workspace_data/upload_blob.py +235 -0
  42. octostar/api/workspace_data/upsert_entities.py +284 -0
  43. octostar/api/workspace_permissions/__init__.py +0 -0
  44. octostar/api/workspace_permissions/get_permissions.py +325 -0
  45. octostar/api/workspace_tags/__init__.py +0 -0
  46. octostar/api/workspace_tags/delete_tag_from_entities.py +141 -0
  47. octostar/api/workspace_tags/tag_entities.py +180 -0
  48. octostar/client.py +492 -0
  49. octostar/errors.py +50 -0
  50. octostar/models/__init__.py +249 -0
  51. octostar/models/acknowledgement.py +74 -0
  52. octostar/models/acknowledgement_with_data.py +82 -0
  53. octostar/models/app_status.py +239 -0
  54. octostar/models/app_status_annotations.py +66 -0
  55. octostar/models/app_status_labels.py +69 -0
  56. octostar/models/app_with_url.py +82 -0
  57. octostar/models/child_processing_status.py +118 -0
  58. octostar/models/delete_entities_response_401.py +74 -0
  59. octostar/models/delete_entities_response_409.py +82 -0
  60. octostar/models/delete_entities_response_500.py +82 -0
  61. octostar/models/delete_stream_response_401.py +74 -0
  62. octostar/models/delete_tag_from_entities_response_401.py +74 -0
  63. octostar/models/deploy_app_json_body.py +90 -0
  64. octostar/models/deploy_app_json_body_secrets.py +65 -0
  65. octostar/models/deploy_app_response_200.py +98 -0
  66. octostar/models/deploy_app_response_200_data.py +60 -0
  67. octostar/models/deploy_app_response_400.py +82 -0
  68. octostar/models/deploy_app_response_403.py +82 -0
  69. octostar/models/deploy_app_response_404.py +82 -0
  70. octostar/models/deploy_app_response_409.py +82 -0
  71. octostar/models/deploy_app_response_500.py +82 -0
  72. octostar/models/entity.py +80 -0
  73. octostar/models/entity_response.py +99 -0
  74. octostar/models/entity_response_s3_urls.py +93 -0
  75. octostar/models/entity_response_s3_urls_additional_property.py +105 -0
  76. octostar/models/entity_response_s3_urls_additional_property_fields.py +114 -0
  77. octostar/models/execute_app_job_json_body.py +151 -0
  78. octostar/models/execute_app_job_json_body_annotation.py +65 -0
  79. octostar/models/execute_app_job_response_401.py +74 -0
  80. octostar/models/fetch_ontology_data_response_200.py +60 -0
  81. octostar/models/fetch_ontology_data_response_401.py +74 -0
  82. octostar/models/fetch_ontology_data_response_500.py +82 -0
  83. octostar/models/get_app_logs_response_401.py +74 -0
  84. octostar/models/get_app_logs_response_404.py +74 -0
  85. octostar/models/get_app_logs_response_500.py +82 -0
  86. octostar/models/get_apps_url_json_body.py +76 -0
  87. octostar/models/get_apps_url_response_401.py +74 -0
  88. octostar/models/get_apps_url_response_500.py +82 -0
  89. octostar/models/get_attachment_response_200.py +74 -0
  90. octostar/models/get_attachment_response_401.py +74 -0
  91. octostar/models/get_files_tree_response_200.py +106 -0
  92. octostar/models/get_files_tree_response_200_status.py +8 -0
  93. octostar/models/get_files_tree_response_400.py +111 -0
  94. octostar/models/get_files_tree_response_400_data.py +60 -0
  95. octostar/models/get_files_tree_response_400_status.py +8 -0
  96. octostar/models/get_files_tree_response_401.py +74 -0
  97. octostar/models/get_files_tree_response_500.py +111 -0
  98. octostar/models/get_files_tree_response_500_data.py +60 -0
  99. octostar/models/get_files_tree_response_500_status.py +8 -0
  100. octostar/models/get_job_logs_response_401.py +74 -0
  101. octostar/models/get_job_logs_response_404.py +74 -0
  102. octostar/models/get_job_logs_response_500.py +82 -0
  103. octostar/models/get_job_progress_response_401.py +74 -0
  104. octostar/models/get_object_response_401.py +74 -0
  105. octostar/models/get_ontologies_response_401.py +74 -0
  106. octostar/models/get_ontologies_response_500.py +81 -0
  107. octostar/models/get_permissions_response_200.py +98 -0
  108. octostar/models/get_permissions_response_400.py +82 -0
  109. octostar/models/get_permissions_response_401.py +74 -0
  110. octostar/models/get_permissions_response_500.py +82 -0
  111. octostar/models/get_processing_status_response_200.py +104 -0
  112. octostar/models/get_processing_status_response_200_data.py +87 -0
  113. octostar/models/get_processing_status_response_400.py +82 -0
  114. octostar/models/get_processing_status_response_500.py +82 -0
  115. octostar/models/get_subscriptions_response_200_item.py +74 -0
  116. octostar/models/get_version_response_200.py +74 -0
  117. octostar/models/get_version_response_404.py +74 -0
  118. octostar/models/get_whoami_response_200.py +129 -0
  119. octostar/models/get_whoami_response_401.py +74 -0
  120. octostar/models/insert_entity.py +114 -0
  121. octostar/models/insert_entity_base.py +266 -0
  122. octostar/models/insert_entity_relationships_item.py +107 -0
  123. octostar/models/insert_entity_request.py +94 -0
  124. octostar/models/internal_server_error.py +82 -0
  125. octostar/models/job_execution_result.py +146 -0
  126. octostar/models/job_status.py +196 -0
  127. octostar/models/job_status_labels.py +60 -0
  128. octostar/models/job_with_url.py +82 -0
  129. octostar/models/kill_job_response_401.py +74 -0
  130. octostar/models/list_app_jobs_response_401.py +74 -0
  131. octostar/models/list_app_jobs_response_500.py +82 -0
  132. octostar/models/list_apps_response_401.py +74 -0
  133. octostar/models/list_apps_response_500.py +82 -0
  134. octostar/models/multi_query_json_body.py +100 -0
  135. octostar/models/multi_query_json_body_queries_item.py +80 -0
  136. octostar/models/multi_query_response_400.py +82 -0
  137. octostar/models/multi_query_response_401.py +74 -0
  138. octostar/models/not_found_error.py +74 -0
  139. octostar/models/octostar_event.py +96 -0
  140. octostar/models/octostar_event_octostar_payload.py +100 -0
  141. octostar/models/octostar_event_octostar_payload_level.py +11 -0
  142. octostar/models/os_notification.py +122 -0
  143. octostar/models/processing_status.py +262 -0
  144. octostar/models/processing_status_code.py +14 -0
  145. octostar/models/progress_request.py +73 -0
  146. octostar/models/publish_notification_response_401.py +74 -0
  147. octostar/models/pull_events_from_stream_response_401.py +74 -0
  148. octostar/models/push_event_to_stream_response_401.py +74 -0
  149. octostar/models/query_json_body.py +101 -0
  150. octostar/models/query_json_body_params.py +60 -0
  151. octostar/models/query_response_400.py +82 -0
  152. octostar/models/query_response_401.py +74 -0
  153. octostar/models/set_job_progress_response_401.py +74 -0
  154. octostar/models/string_to_value_label_map.py +99 -0
  155. octostar/models/string_to_value_label_map_data.py +89 -0
  156. octostar/models/string_to_value_label_map_data_additional_property.py +80 -0
  157. octostar/models/successful_get_tags.py +103 -0
  158. octostar/models/successful_insertion.py +98 -0
  159. octostar/models/tag_entities_response_401.py +74 -0
  160. octostar/models/toast_level.py +11 -0
  161. octostar/models/toast_response_401.py +74 -0
  162. octostar/models/undeploy_app_response_401.py +74 -0
  163. octostar/models/update_processing_status_response_200.py +82 -0
  164. octostar/models/update_processing_status_response_400.py +82 -0
  165. octostar/models/update_processing_status_response_500.py +82 -0
  166. octostar/models/upsert_entities_response_401.py +74 -0
  167. octostar/models/upsert_entity.py +114 -0
  168. octostar/models/upsert_entity_base.py +266 -0
  169. octostar/models/upsert_entity_relationships_item.py +107 -0
  170. octostar/py.typed +1 -0
  171. octostar/types.py +54 -0
  172. octostar/utils/__init__.py +15 -0
  173. octostar/utils/chat/__init__.py +0 -0
  174. octostar/utils/chat/chat.py +513 -0
  175. octostar/utils/chat/detokenize.py +105 -0
  176. octostar/utils/chat/get_default_model.py +50 -0
  177. octostar/utils/chat/list_models.py +91 -0
  178. octostar/utils/chat/tokenize.py +105 -0
  179. octostar/utils/commons.py +226 -0
  180. octostar/utils/exceptions.py +134 -0
  181. octostar/utils/jobs/__init__.py +0 -0
  182. octostar/utils/jobs/apps/__init__.py +0 -0
  183. octostar/utils/jobs/apps/deploy_app.py +81 -0
  184. octostar/utils/jobs/apps/execute_app_job.py +114 -0
  185. octostar/utils/jobs/apps/get_app_logs.py +113 -0
  186. octostar/utils/jobs/apps/get_app_secret.py +102 -0
  187. octostar/utils/jobs/apps/get_apps_url.py +73 -0
  188. octostar/utils/jobs/apps/list_app_jobs.py +62 -0
  189. octostar/utils/jobs/apps/list_apps.py +126 -0
  190. octostar/utils/jobs/apps/undeploy_app.py +48 -0
  191. octostar/utils/jobs/get_job_logs.py +113 -0
  192. octostar/utils/jobs/get_job_progress.py +76 -0
  193. octostar/utils/jobs/kill_job.py +47 -0
  194. octostar/utils/jobs/set_job_progress.py +67 -0
  195. octostar/utils/meta/__init__.py +0 -0
  196. octostar/utils/meta/get_version.py +30 -0
  197. octostar/utils/meta/get_whoami.py +30 -0
  198. octostar/utils/notifications/__init__.py +0 -0
  199. octostar/utils/notifications/delete_stream.py +58 -0
  200. octostar/utils/notifications/get_my_subscriptions.py +49 -0
  201. octostar/utils/notifications/publish_notification.py +73 -0
  202. octostar/utils/notifications/pull_event_from_stream.py +63 -0
  203. octostar/utils/notifications/pull_events_from_stream.py +64 -0
  204. octostar/utils/notifications/push_event_to_stream.py +109 -0
  205. octostar/utils/notifications/push_events_to_stream.py +137 -0
  206. octostar/utils/notifications/toast.py +92 -0
  207. octostar/utils/ontology/__init__.py +10 -0
  208. octostar/utils/ontology/fetch_ontology_data.py +141 -0
  209. octostar/utils/ontology/get_ontologies.py +55 -0
  210. octostar/utils/ontology/multiquery_ontology.py +287 -0
  211. octostar/utils/ontology/query_ontology.py +186 -0
  212. octostar/utils/pipeline/__init__.py +1 -0
  213. octostar/utils/pipeline/get_processing_status.py +230 -0
  214. octostar/utils/pipeline/update_processing_status.py +286 -0
  215. octostar/utils/search/__init__.py +11 -0
  216. octostar/utils/search/bulk_update.py +138 -0
  217. octostar/utils/search/count.py +117 -0
  218. octostar/utils/search/get_entity_annotations.py +304 -0
  219. octostar/utils/search/get_index_definition.py +111 -0
  220. octostar/utils/search/multi_search.py +129 -0
  221. octostar/utils/workspace/__init__.py +0 -0
  222. octostar/utils/workspace/delete_entities.py +247 -0
  223. octostar/utils/workspace/delete_entity.py +81 -0
  224. octostar/utils/workspace/delete_relationship.py +78 -0
  225. octostar/utils/workspace/delete_relationships.py +85 -0
  226. octostar/utils/workspace/delete_temporary_blob.py +85 -0
  227. octostar/utils/workspace/extract_entities.py +140 -0
  228. octostar/utils/workspace/get_filepath_from_item.py +85 -0
  229. octostar/utils/workspace/get_filepaths_from_items.py +100 -0
  230. octostar/utils/workspace/get_files_tree.py +102 -0
  231. octostar/utils/workspace/get_item_from_filepath.py +102 -0
  232. octostar/utils/workspace/get_items_from_filepaths.py +108 -0
  233. octostar/utils/workspace/linkcharts/__init__.py +0 -0
  234. octostar/utils/workspace/linkcharts/create_linkchart.py +241 -0
  235. octostar/utils/workspace/permissions/PermissionLevel.py +8 -0
  236. octostar/utils/workspace/permissions/__init__.py +1 -0
  237. octostar/utils/workspace/permissions/get_permissions.py +81 -0
  238. octostar/utils/workspace/read_attachment.py +284 -0
  239. octostar/utils/workspace/read_file.py +113 -0
  240. octostar/utils/workspace/read_temporary_blob.py +428 -0
  241. octostar/utils/workspace/saved_searches/__init__.py +0 -0
  242. octostar/utils/workspace/saved_searches/create_saved_search.py +183 -0
  243. octostar/utils/workspace/tags/__init__.py +0 -0
  244. octostar/utils/workspace/tags/delete_tag_from_entities.py +96 -0
  245. octostar/utils/workspace/tags/tag_entities.py +175 -0
  246. octostar/utils/workspace/upsert_entities.py +268 -0
  247. octostar/utils/workspace/upsert_entity.py +110 -0
  248. octostar/utils/workspace/upsert_relationship.py +128 -0
  249. octostar/utils/workspace/upsert_relationships.py +194 -0
  250. octostar/utils/workspace/write_attachment.py +263 -0
  251. octostar/utils/workspace/write_file.py +335 -0
  252. octostar/utils/workspace/write_temporary_blob.py +218 -0
  253. octostar_python_client-0.1.759.dist-info/METADATA +159 -0
  254. octostar_python_client-0.1.759.dist-info/RECORD +257 -0
  255. octostar_python_client-0.1.759.dist-info/WHEEL +5 -0
  256. octostar_python_client-0.1.759.dist-info/licenses/LICENSE +21 -0
  257. octostar_python_client-0.1.759.dist-info/top_level.txt +1 -0
@@ -0,0 +1,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,8 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class PermissionLevel(IntEnum):
5
+ NONE = 0
6
+ READ = 1
7
+ WRITE = 2
8
+ ADMIN = 4
@@ -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