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,91 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from ...client import Client, get_default_client
|
|
4
|
+
from ..exceptions import ApiConnectionError
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from ..commons import network_retry_strategy
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _prepare_list_models_request(client):
|
|
10
|
+
endpoint_url = f"{client.get_base_url_v1()}/api/v1/chat/models"
|
|
11
|
+
headers = {
|
|
12
|
+
"Authorization": f"Bearer {client.token}",
|
|
13
|
+
"x-ontology": client.ontology,
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
"url": endpoint_url,
|
|
17
|
+
"headers": headers,
|
|
18
|
+
"timeout": 60,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def sync(
|
|
23
|
+
client: Optional[Client] = None,
|
|
24
|
+
) -> list[dict]:
|
|
25
|
+
"""
|
|
26
|
+
# List the available AI chat models.
|
|
27
|
+
|
|
28
|
+
## Arguments
|
|
29
|
+
- `client`: The Client with which to connect to Octostar. If None, the default one is used.
|
|
30
|
+
|
|
31
|
+
## Returns
|
|
32
|
+
A list of information for each available AI model.
|
|
33
|
+
|
|
34
|
+
## Raises
|
|
35
|
+
- `ApiConnectionError`: If the query was unsuccessful.
|
|
36
|
+
"""
|
|
37
|
+
if not client:
|
|
38
|
+
client = get_default_client()
|
|
39
|
+
response = None
|
|
40
|
+
try:
|
|
41
|
+
for attempt in network_retry_strategy(retries=3):
|
|
42
|
+
with attempt:
|
|
43
|
+
with httpx.Client() as httpx_client:
|
|
44
|
+
response = httpx_client.get(**_prepare_list_models_request(client))
|
|
45
|
+
response.raise_for_status()
|
|
46
|
+
try:
|
|
47
|
+
response = response.json()
|
|
48
|
+
except Exception as e:
|
|
49
|
+
raise ConnectionError(
|
|
50
|
+
f"Unparsable response format {response.content} with exception {e}"
|
|
51
|
+
)
|
|
52
|
+
except Exception:
|
|
53
|
+
raise ApiConnectionError("list_models", response, client)
|
|
54
|
+
return response
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async def asyncio(
|
|
58
|
+
client: Optional[Client] = None,
|
|
59
|
+
) -> list[dict]:
|
|
60
|
+
"""
|
|
61
|
+
# List asynchronously the available AI chat models.
|
|
62
|
+
|
|
63
|
+
## Arguments
|
|
64
|
+
- `client`: The Client with which to connect to Octostar. If None, the default one is used.
|
|
65
|
+
|
|
66
|
+
## Returns
|
|
67
|
+
A list of information for each available AI model.
|
|
68
|
+
|
|
69
|
+
## Raises
|
|
70
|
+
- `ApiConnectionError`: If the query was unsuccessful.
|
|
71
|
+
"""
|
|
72
|
+
if not client:
|
|
73
|
+
client = get_default_client()
|
|
74
|
+
response = None
|
|
75
|
+
try:
|
|
76
|
+
for attempt in network_retry_strategy(retries=3):
|
|
77
|
+
with attempt:
|
|
78
|
+
async with httpx.AsyncClient() as httpx_client:
|
|
79
|
+
response = await httpx_client.get(
|
|
80
|
+
**_prepare_list_models_request(client)
|
|
81
|
+
)
|
|
82
|
+
response.raise_for_status()
|
|
83
|
+
try:
|
|
84
|
+
response = response.json()
|
|
85
|
+
except Exception as e:
|
|
86
|
+
raise ConnectionError(
|
|
87
|
+
f"Unparsable response format {response.content} with exception {e}"
|
|
88
|
+
)
|
|
89
|
+
except Exception:
|
|
90
|
+
raise ApiConnectionError("list_models", response, client)
|
|
91
|
+
return response
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from ...client import Client, get_default_client
|
|
4
|
+
from ..exceptions import ApiConnectionError
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from ..commons import network_retry_strategy
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _prepare_tokenize_request(client, text, model_name):
|
|
10
|
+
endpoint_url = f"{client.get_base_url_v1()}/api/v2/ai/llm/tokenize"
|
|
11
|
+
headers = {
|
|
12
|
+
"Authorization": f"Bearer {client.token}",
|
|
13
|
+
"x-ontology": client.ontology,
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
"url": endpoint_url,
|
|
17
|
+
"headers": headers,
|
|
18
|
+
"timeout": 60,
|
|
19
|
+
"json": {
|
|
20
|
+
"text": text,
|
|
21
|
+
"model_name": model_name,
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def sync(
|
|
27
|
+
text: str,
|
|
28
|
+
model_name: str,
|
|
29
|
+
client: Optional[Client] = None,
|
|
30
|
+
) -> list[int]:
|
|
31
|
+
"""
|
|
32
|
+
# Tokenize a string into a list of LLM tokens.
|
|
33
|
+
|
|
34
|
+
## Arguments
|
|
35
|
+
- `text`: The input string to tokenize.
|
|
36
|
+
- `model_name`: The model with which to tokenize the input text.
|
|
37
|
+
- `client`: The Client with which to connect to Octostar. If None, the default one is used.
|
|
38
|
+
|
|
39
|
+
## Returns
|
|
40
|
+
A list of tokens.
|
|
41
|
+
|
|
42
|
+
## Raises
|
|
43
|
+
- `ApiConnectionError`: If the query was unsuccessful.
|
|
44
|
+
"""
|
|
45
|
+
if not client:
|
|
46
|
+
client = get_default_client()
|
|
47
|
+
response = None
|
|
48
|
+
try:
|
|
49
|
+
for attempt in network_retry_strategy(retries=3):
|
|
50
|
+
with attempt:
|
|
51
|
+
with httpx.Client() as httpx_client:
|
|
52
|
+
response = httpx_client.post(
|
|
53
|
+
**_prepare_tokenize_request(client, text, model_name)
|
|
54
|
+
)
|
|
55
|
+
response.raise_for_status()
|
|
56
|
+
try:
|
|
57
|
+
response = response.json()["tokens"]
|
|
58
|
+
except Exception as e:
|
|
59
|
+
raise ConnectionError(
|
|
60
|
+
f"Unparsable response format {response.content} with exception {e}"
|
|
61
|
+
)
|
|
62
|
+
except Exception:
|
|
63
|
+
raise ApiConnectionError("tokenize", response, client)
|
|
64
|
+
return response # pyright: ignore[reportReturnType]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def asyncio(
|
|
68
|
+
text: str,
|
|
69
|
+
model_name: str,
|
|
70
|
+
client: Optional[Client] = None,
|
|
71
|
+
) -> list[int]:
|
|
72
|
+
"""
|
|
73
|
+
# Tokenize a string into a list of LLM tokens.
|
|
74
|
+
|
|
75
|
+
## Arguments
|
|
76
|
+
- `text`: The input string to tokenize.
|
|
77
|
+
- `model_name`: The model with which to tokenize the input text.
|
|
78
|
+
- `client`: The Client with which to connect to Octostar. If None, the default one is used.
|
|
79
|
+
|
|
80
|
+
## Returns
|
|
81
|
+
A list of tokens.
|
|
82
|
+
|
|
83
|
+
## Raises
|
|
84
|
+
- `ApiConnectionError`: If the query was unsuccessful.
|
|
85
|
+
"""
|
|
86
|
+
if not client:
|
|
87
|
+
client = get_default_client()
|
|
88
|
+
response = None
|
|
89
|
+
try:
|
|
90
|
+
for attempt in network_retry_strategy(retries=3):
|
|
91
|
+
with attempt:
|
|
92
|
+
async with httpx.AsyncClient() as httpx_client:
|
|
93
|
+
response = await httpx_client.post(
|
|
94
|
+
**_prepare_tokenize_request(client, text, model_name)
|
|
95
|
+
)
|
|
96
|
+
response.raise_for_status()
|
|
97
|
+
try:
|
|
98
|
+
response = response.json()["tokens"]
|
|
99
|
+
except Exception as e:
|
|
100
|
+
raise ConnectionError(
|
|
101
|
+
f"Unparsable response format {response.content} with exception {e}"
|
|
102
|
+
)
|
|
103
|
+
except Exception:
|
|
104
|
+
raise ApiConnectionError("tokenize", response, client)
|
|
105
|
+
return response # pyright: ignore[reportReturnType]
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Callable, Optional, List
|
|
3
|
+
import time
|
|
4
|
+
from tenacity import (
|
|
5
|
+
Retrying,
|
|
6
|
+
stop_after_attempt,
|
|
7
|
+
wait_exponential_jitter,
|
|
8
|
+
retry_if_exception,
|
|
9
|
+
)
|
|
10
|
+
import httpx
|
|
11
|
+
from calendar import timegm
|
|
12
|
+
from pytimeparse import parse as timeparse
|
|
13
|
+
|
|
14
|
+
from .exceptions import ApiConnectionError, StopAsyncIterationWithResult
|
|
15
|
+
from ..types import Response
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
TIMESTAMP_SKEW = 10
|
|
19
|
+
|
|
20
|
+
network_retry_strategy = lambda retries=3, retry_type=wait_exponential_jitter: Retrying(
|
|
21
|
+
stop=stop_after_attempt(retries),
|
|
22
|
+
wait=lambda state: network_wait_strategy(state, retry_type),
|
|
23
|
+
retry=retry_if_exception(network_retry_condition),
|
|
24
|
+
reraise=True,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def network_get_retry_after(exception):
|
|
29
|
+
if not isinstance(exception, ApiConnectionError):
|
|
30
|
+
return None
|
|
31
|
+
resp = getattr(exception, "response", None)
|
|
32
|
+
if not resp or not resp.headers:
|
|
33
|
+
return None
|
|
34
|
+
headers = resp.headers
|
|
35
|
+
headers = {k.lower(): v for k, v in resp.headers.items()}
|
|
36
|
+
wait_candidates = []
|
|
37
|
+
|
|
38
|
+
# Standard Retry-After header (RFC 7231)
|
|
39
|
+
retry_after = headers.get("retry-after")
|
|
40
|
+
if retry_after:
|
|
41
|
+
retry_after = retry_after.strip()
|
|
42
|
+
if retry_after.replace(".", "", 1).isdigit():
|
|
43
|
+
wait_candidates.append(float(retry_after))
|
|
44
|
+
else:
|
|
45
|
+
try:
|
|
46
|
+
retry_time = timegm(
|
|
47
|
+
time.strptime(retry_after, "%a, %d %b %Y %H:%M:%S GMT")
|
|
48
|
+
)
|
|
49
|
+
delta = retry_time - time.time()
|
|
50
|
+
if delta > 0:
|
|
51
|
+
wait_candidates.append(delta)
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
# OpenAI / LiteLLM rate-limit reset headers
|
|
56
|
+
for key in ("x-ratelimit-reset-requests", "x-ratelimit-reset-tokens"):
|
|
57
|
+
val = headers.get(key)
|
|
58
|
+
if not val:
|
|
59
|
+
continue
|
|
60
|
+
val = val.strip()
|
|
61
|
+
if val.replace(".", "", 1).isdigit():
|
|
62
|
+
numeric = float(val)
|
|
63
|
+
now = time.time()
|
|
64
|
+
if numeric > now + TIMESTAMP_SKEW:
|
|
65
|
+
delta = numeric - now
|
|
66
|
+
if delta > 0:
|
|
67
|
+
wait_candidates.append(delta)
|
|
68
|
+
else:
|
|
69
|
+
if numeric > 0:
|
|
70
|
+
wait_candidates.append(numeric)
|
|
71
|
+
else:
|
|
72
|
+
try:
|
|
73
|
+
delta = timeparse(val)
|
|
74
|
+
if delta > 0:
|
|
75
|
+
wait_candidates.append(delta)
|
|
76
|
+
except Exception:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
if wait_candidates:
|
|
80
|
+
return max(wait_candidates)
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def network_retry_condition(exception):
|
|
85
|
+
if isinstance(exception, httpx.RequestError):
|
|
86
|
+
return True
|
|
87
|
+
if isinstance(exception, ApiConnectionError):
|
|
88
|
+
if hasattr(exception, "response") and exception.response:
|
|
89
|
+
sc = exception.response.status_code
|
|
90
|
+
return (300 <= sc <= 399) or (500 <= sc <= 599) or sc in [429, 408]
|
|
91
|
+
return True
|
|
92
|
+
if isinstance(exception, ConnectionError):
|
|
93
|
+
return True
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def network_wait_strategy(retry_state, default_retry_type=wait_exponential_jitter):
|
|
98
|
+
exception = retry_state.outcome.exception()
|
|
99
|
+
retry_after = network_get_retry_after(exception)
|
|
100
|
+
if retry_after is not None:
|
|
101
|
+
return retry_after
|
|
102
|
+
else:
|
|
103
|
+
return default_retry_type()(retry_state)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class FailCondition:
|
|
107
|
+
def __init__(self, callable: Callable[[Response], bool]):
|
|
108
|
+
self.callable = callable
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ChunksFailCondition:
|
|
112
|
+
def __init__(self, callable: Callable[[List[Response]], bool]):
|
|
113
|
+
self.callable = callable
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ChunksEndCondition:
|
|
117
|
+
def __init__(
|
|
118
|
+
self,
|
|
119
|
+
callable: Callable[[List[Response]], bool],
|
|
120
|
+
timeout: Optional[float] = None,
|
|
121
|
+
):
|
|
122
|
+
self.callable = callable
|
|
123
|
+
self.timeout = timeout
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class TimeoutAsyncGenerator:
|
|
127
|
+
def __init__(self, generator, timeout):
|
|
128
|
+
self.generator = generator
|
|
129
|
+
self.timeout = timeout
|
|
130
|
+
self.start_time = time.monotonic()
|
|
131
|
+
|
|
132
|
+
async def __aiter__(self):
|
|
133
|
+
gen_iter = self.generator.__aiter__()
|
|
134
|
+
while True:
|
|
135
|
+
elapsed_time = time.monotonic() - self.start_time
|
|
136
|
+
remaining_time = self.timeout - elapsed_time
|
|
137
|
+
if remaining_time <= 0:
|
|
138
|
+
raise asyncio.TimeoutError()
|
|
139
|
+
try:
|
|
140
|
+
item = await asyncio.wait_for(gen_iter.__anext__(), remaining_time)
|
|
141
|
+
yield item
|
|
142
|
+
except StopAsyncIteration:
|
|
143
|
+
break
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def sync_request(
|
|
147
|
+
client,
|
|
148
|
+
module,
|
|
149
|
+
fail_condition=None,
|
|
150
|
+
retry_strategy=network_retry_strategy,
|
|
151
|
+
**request_kwargs,
|
|
152
|
+
):
|
|
153
|
+
if not fail_condition:
|
|
154
|
+
fail_condition = FailCondition(callable=lambda resp: resp.status_code != 200)
|
|
155
|
+
for attempt in retry_strategy():
|
|
156
|
+
with attempt:
|
|
157
|
+
response = module.sync_detailed(**request_kwargs, client=client)
|
|
158
|
+
if fail_condition.callable(response):
|
|
159
|
+
raise ApiConnectionError(module.__name__, response, client)
|
|
160
|
+
return response
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
async def asyncio_request(
|
|
164
|
+
client,
|
|
165
|
+
module,
|
|
166
|
+
fail_condition=None,
|
|
167
|
+
retry_strategy=network_retry_strategy,
|
|
168
|
+
**request_kwargs,
|
|
169
|
+
):
|
|
170
|
+
if not fail_condition:
|
|
171
|
+
fail_condition = FailCondition(callable=lambda resp: resp.status_code != 200)
|
|
172
|
+
for attempt in retry_strategy():
|
|
173
|
+
with attempt:
|
|
174
|
+
response = await module.asyncio_detailed(**request_kwargs, client=client)
|
|
175
|
+
if fail_condition.callable(response):
|
|
176
|
+
raise ApiConnectionError(module.__name__, response, client)
|
|
177
|
+
return response
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
async def streaming_request(
|
|
181
|
+
client,
|
|
182
|
+
module,
|
|
183
|
+
fail_condition=None,
|
|
184
|
+
end_condition=None,
|
|
185
|
+
retry_strategy=network_retry_strategy,
|
|
186
|
+
**request_kwargs,
|
|
187
|
+
):
|
|
188
|
+
async def _collect_chunks(
|
|
189
|
+
generator, collected_chunks, fail_condition, end_condition
|
|
190
|
+
):
|
|
191
|
+
if end_condition.timeout:
|
|
192
|
+
generator = TimeoutAsyncGenerator(generator, end_condition.timeout)
|
|
193
|
+
try:
|
|
194
|
+
async for chunk in generator:
|
|
195
|
+
if fail_condition.callable(collected_chunks + [chunk]):
|
|
196
|
+
raise ApiConnectionError(module.__name__, chunk, client)
|
|
197
|
+
else:
|
|
198
|
+
collected_chunks.append(chunk)
|
|
199
|
+
if end_condition.callable(collected_chunks):
|
|
200
|
+
raise StopAsyncIterationWithResult(value=collected_chunks)
|
|
201
|
+
else:
|
|
202
|
+
yield chunk
|
|
203
|
+
except asyncio.TimeoutError:
|
|
204
|
+
raise StopAsyncIterationWithResult(value=collected_chunks)
|
|
205
|
+
|
|
206
|
+
if not fail_condition:
|
|
207
|
+
fail_condition = ChunksFailCondition(
|
|
208
|
+
callable=lambda resps: not resps or resps[0].status_code != 200
|
|
209
|
+
)
|
|
210
|
+
if not end_condition:
|
|
211
|
+
end_condition = ChunksEndCondition(callable=lambda resps: False)
|
|
212
|
+
collected_chunks = []
|
|
213
|
+
for attempt in retry_strategy():
|
|
214
|
+
with attempt:
|
|
215
|
+
generator = module.streaming_detailed(**request_kwargs, client=client)
|
|
216
|
+
generator = _collect_chunks(
|
|
217
|
+
generator, collected_chunks, fail_condition, end_condition
|
|
218
|
+
)
|
|
219
|
+
async for chunk in generator:
|
|
220
|
+
yield chunk
|
|
221
|
+
raise StopAsyncIterationWithResult(value=collected_chunks)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def streaming_get_full_content(responses):
|
|
225
|
+
chunks = [part.content for part in responses if part.content.strip()]
|
|
226
|
+
return b"".join(chunks)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class StopAsyncIterationWithResult(Exception):
|
|
8
|
+
def __init__(self, value):
|
|
9
|
+
self.value = value
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ApiConnectionError(ConnectionError):
|
|
13
|
+
def __init__(self, method_name, response, client, max_message_length=10000):
|
|
14
|
+
self.method_name = method_name
|
|
15
|
+
self.response = response
|
|
16
|
+
self.client = client
|
|
17
|
+
|
|
18
|
+
message = (
|
|
19
|
+
"{method_name} failed with status code {code} and response\n{response}"
|
|
20
|
+
)
|
|
21
|
+
status_code = getattr(response, "status_code", "Unknown")
|
|
22
|
+
content = getattr(response, "content", "No content available")
|
|
23
|
+
message = message.format(
|
|
24
|
+
method_name=method_name, code=status_code, response=content
|
|
25
|
+
)
|
|
26
|
+
if len(message) > max_message_length:
|
|
27
|
+
message = message[: max_message_length - 3] + "..."
|
|
28
|
+
super().__init__(message)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# FIXME: TEAM-6154 — drop this regex once the server returns the
|
|
32
|
+
# rejected field as a structured attribute on EntityError. Today
|
|
33
|
+
# UpsertResponse carries each rejection only as a free-form
|
|
34
|
+
# ``error: str``, so we extract the field name client-side to
|
|
35
|
+
# populate ``invalid_fields``.
|
|
36
|
+
#
|
|
37
|
+
# Matches per-field server validation rejections inside an entity's error text:
|
|
38
|
+
# "Invalid <type> value for field '<name>'[: '<value>']"
|
|
39
|
+
_FIELD_FAILURE_RE = re.compile(
|
|
40
|
+
r"Invalid\s+\S+\s+value\s+for\s+field\s+'(?P<field>[^']+)'",
|
|
41
|
+
re.IGNORECASE,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# FIXME: TEAM-6154 — drop this regex too, same task as
|
|
45
|
+
# _FIELD_FAILURE_RE above. The server should expose the rejected
|
|
46
|
+
# reserved-keyword token as a structured attribute on EntityError.
|
|
47
|
+
#
|
|
48
|
+
# Matches "reserved keyword" mentions inside an entity's error text, capturing
|
|
49
|
+
# the offending token when it appears quoted (e.g. ``reserved keyword 'call'``).
|
|
50
|
+
# Some backends omit the quotes, so callers should also fall back to
|
|
51
|
+
# ``"reserved keyword" in err["error"]`` for boolean detection.
|
|
52
|
+
_RESERVED_KEYWORD_RE = re.compile(
|
|
53
|
+
r"reserved\s+keyword[^'\"]*['\"](?P<keyword>[^'\"]+)['\"]",
|
|
54
|
+
re.IGNORECASE,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ApiValidationError(ApiConnectionError):
|
|
59
|
+
"""Raised when the server accepted the upsert request but rejected one
|
|
60
|
+
or more entities server-side.
|
|
61
|
+
|
|
62
|
+
Subclass of :class:`ApiConnectionError`, so handlers catching the
|
|
63
|
+
parent class still receive validation failures.
|
|
64
|
+
|
|
65
|
+
The per-entity breakdown is exposed via structured attributes:
|
|
66
|
+
|
|
67
|
+
Attributes:
|
|
68
|
+
entity_errors: ``list[dict]`` — ``[{"entity_id": str, "error":
|
|
69
|
+
str}, ...]``, the per-entity error rows as returned by the
|
|
70
|
+
server. Use this when you need the raw message text for
|
|
71
|
+
each failing entity.
|
|
72
|
+
missing_entity_ids: ``set[str]`` — IDs that were sent but appear
|
|
73
|
+
in neither ``response.entities`` nor ``response.errors``.
|
|
74
|
+
The server has no record of these: neither written nor
|
|
75
|
+
explicitly rejected.
|
|
76
|
+
mismatched_keys: ``list[tuple[str, str]]`` — ``(entity_id,
|
|
77
|
+
os_workspace)`` pairs the server returned under an
|
|
78
|
+
``os_workspace`` different from the one you sent. Typically
|
|
79
|
+
indicates a stale record was matched; refetching is the
|
|
80
|
+
usual recovery.
|
|
81
|
+
invalid_fields: ``dict[str, list[str]]`` — ``{entity_id:
|
|
82
|
+
[field_name, ...]}`` covering per-field rejections
|
|
83
|
+
(malformed datetime, bad number, etc.). To recover, drop
|
|
84
|
+
the listed fields from the payload for each entity and
|
|
85
|
+
re-upsert.
|
|
86
|
+
reserved_keyword_entity_types: ``set[str]`` — entity-type
|
|
87
|
+
tokens the server's query parser flagged as reserved
|
|
88
|
+
keywords (e.g. Cypher ``call``, ``match``). Resolution is
|
|
89
|
+
usually renaming the entity type at the source ontology.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(
|
|
93
|
+
self,
|
|
94
|
+
method_name,
|
|
95
|
+
response,
|
|
96
|
+
client,
|
|
97
|
+
*,
|
|
98
|
+
entity_errors: list[dict[str, Any]] | None = None,
|
|
99
|
+
missing_entity_ids: set[str] | None = None,
|
|
100
|
+
mismatched_keys: list[tuple[str, str]] | None = None,
|
|
101
|
+
max_message_length: int = 10000,
|
|
102
|
+
):
|
|
103
|
+
self.entity_errors: list[dict[str, Any]] = list(entity_errors or [])
|
|
104
|
+
self.missing_entity_ids: set[str] = set(missing_entity_ids or ())
|
|
105
|
+
self.mismatched_keys: list[tuple[str, str]] = list(mismatched_keys or ())
|
|
106
|
+
super().__init__(
|
|
107
|
+
method_name, response, client, max_message_length=max_message_length
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def invalid_fields(self) -> dict[str, list[str]]:
|
|
112
|
+
"""Per-entity invalid field names parsed from the server's error strings."""
|
|
113
|
+
result: dict[str, list[str]] = {}
|
|
114
|
+
for err in self.entity_errors:
|
|
115
|
+
entity_id = err.get("entity_id")
|
|
116
|
+
error_text = err.get("error") or ""
|
|
117
|
+
if not entity_id:
|
|
118
|
+
continue
|
|
119
|
+
for m in _FIELD_FAILURE_RE.finditer(error_text):
|
|
120
|
+
bucket = result.setdefault(entity_id, [])
|
|
121
|
+
field = m.group("field")
|
|
122
|
+
if field not in bucket:
|
|
123
|
+
bucket.append(field)
|
|
124
|
+
return result
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def reserved_keyword_entity_types(self) -> set[str]:
|
|
128
|
+
"""Entity-type tokens extracted from quoted "reserved keyword '<x>'" mentions."""
|
|
129
|
+
result: set[str] = set()
|
|
130
|
+
for err in self.entity_errors:
|
|
131
|
+
error_text = err.get("error") or ""
|
|
132
|
+
for m in _RESERVED_KEYWORD_RE.finditer(error_text):
|
|
133
|
+
result.add(m.group("keyword"))
|
|
134
|
+
return result
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
_logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
from ....api.apps import deploy_app
|
|
8
|
+
from ....models.deploy_app_json_body import DeployAppJsonBody
|
|
9
|
+
from ....models.deploy_app_json_body_secrets import DeployAppJsonBodySecrets
|
|
10
|
+
from ....client import Client
|
|
11
|
+
from ...exceptions import ApiConnectionError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def sync(
|
|
15
|
+
workspace_id: str,
|
|
16
|
+
os_entity_uid: str,
|
|
17
|
+
secrets: Optional[dict[str, Any]] = None,
|
|
18
|
+
client: Client = None,
|
|
19
|
+
):
|
|
20
|
+
"""
|
|
21
|
+
# Deploys the App defined by a given workspace and entity (the app root folder).
|
|
22
|
+
|
|
23
|
+
## Arguments
|
|
24
|
+
- `workspace_id`: The ID of the workspace containing the app folder.
|
|
25
|
+
- `os_entity_uid`: The entity ID of the app folder.
|
|
26
|
+
- `secrets`: An optional key-value pair of secrets to deploy the app with.
|
|
27
|
+
- `client`: The Client with which to connect to Octostar. If None, the default one is used.
|
|
28
|
+
|
|
29
|
+
## Returns
|
|
30
|
+
The app entity.
|
|
31
|
+
|
|
32
|
+
## Raises
|
|
33
|
+
- `ApiConnectionError`: If the request was unsuccessful on the server.
|
|
34
|
+
"""
|
|
35
|
+
if not secrets:
|
|
36
|
+
secrets = {}
|
|
37
|
+
secrets_body = DeployAppJsonBody(DeployAppJsonBodySecrets(**secrets))
|
|
38
|
+
response = deploy_app.sync_detailed(
|
|
39
|
+
workspace=workspace_id,
|
|
40
|
+
os_entity_uid=os_entity_uid,
|
|
41
|
+
json_body=secrets_body,
|
|
42
|
+
client=client,
|
|
43
|
+
)
|
|
44
|
+
if response.status_code != 200:
|
|
45
|
+
raise ApiConnectionError("deploy_app", response, client)
|
|
46
|
+
return response.parsed
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def asyncio(
|
|
50
|
+
workspace_id: str,
|
|
51
|
+
os_entity_uid: str,
|
|
52
|
+
secrets: Optional[dict[str, Any]] = None,
|
|
53
|
+
client: Client = None,
|
|
54
|
+
):
|
|
55
|
+
"""
|
|
56
|
+
# Deploys asynchronously the App defined by a given workspace and entity (the app root folder).
|
|
57
|
+
|
|
58
|
+
## Arguments
|
|
59
|
+
- `workspace_id`: The ID of the workspace containing the app folder.
|
|
60
|
+
- `os_entity_uid`: The entity ID of the app folder.
|
|
61
|
+
- `secrets`: An optional key-value pair of secrets to deploy the app with.
|
|
62
|
+
- `client`: The Client with which to connect to Octostar. If None, the default one is used.
|
|
63
|
+
|
|
64
|
+
## Returns
|
|
65
|
+
The app entity.
|
|
66
|
+
|
|
67
|
+
## Raises
|
|
68
|
+
- `ApiConnectionError`: If the request was unsuccessful on the server.
|
|
69
|
+
"""
|
|
70
|
+
if not secrets:
|
|
71
|
+
secrets = {}
|
|
72
|
+
secrets_body = DeployAppJsonBody(DeployAppJsonBodySecrets(**secrets))
|
|
73
|
+
response = await deploy_app.asyncio_detailed(
|
|
74
|
+
workspace=workspace_id,
|
|
75
|
+
os_entity_uid=os_entity_uid,
|
|
76
|
+
json_body=secrets_body,
|
|
77
|
+
client=client,
|
|
78
|
+
)
|
|
79
|
+
if response.status_code != 200:
|
|
80
|
+
raise ApiConnectionError("deploy_app", response, client)
|
|
81
|
+
return response.parsed
|