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