superwise-api 4.16.0__tar.gz → 4.18.0__tar.gz
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.
- {superwise_api-4.16.0 → superwise_api-4.18.0}/PKG-INFO +3 -1
- {superwise_api-4.16.0 → superwise_api-4.18.0}/pyproject.toml +3 -1
- superwise_api-4.18.0/superwise_api/async_client/__init__.py +3 -0
- superwise_api-4.18.0/superwise_api/async_client/client.py +74 -0
- superwise_api-4.18.0/superwise_api/async_client/entities/__init__.py +4 -0
- superwise_api-4.18.0/superwise_api/async_client/entities/dataset.py +29 -0
- superwise_api-4.18.0/superwise_api/async_client/entities/sentinel.py +83 -0
- superwise_api-4.18.0/superwise_api/async_client/transport.py +76 -0
- superwise_api-4.18.0/superwise_api/auth.py +40 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/client/api_client.py +25 -11
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/client/rest.py +26 -12
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/agent.py +139 -17
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/dataset.py +73 -43
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/destination.py +56 -7
- superwise_api-4.18.0/superwise_api/entities/sentinel.py +141 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/agent/agent.py +50 -12
- superwise_api-4.18.0/superwise_api/models/agent/feedback.py +16 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/agent/playground.py +3 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dataset/dataset.py +4 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dataset/dataset_schema.py +11 -0
- superwise_api-4.18.0/superwise_api/models/destination/destination.py +106 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/guardrails/guardrails.py +16 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/integration/integration.py +3 -0
- superwise_api-4.18.0/superwise_api/models/sentinel/sentinel.py +26 -0
- superwise_api-4.18.0/superwise_api/models/tool/__init__.py +0 -0
- superwise_api-4.18.0/superwise_api/request_builders/__init__.py +0 -0
- superwise_api-4.18.0/superwise_api/request_builders/dataset_request_builder.py +63 -0
- superwise_api-4.18.0/superwise_api/request_builders/requests.py +87 -0
- superwise_api-4.18.0/superwise_api/request_builders/sentinel_request_builder.py +124 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/superwise_client.py +8 -27
- superwise_api-4.16.0/superwise_api/models/agent/feedback.py +0 -41
- superwise_api-4.16.0/superwise_api/models/destination/destination.py +0 -37
- {superwise_api-4.16.0 → superwise_api-4.18.0}/README.md +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/client/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/client/api_response.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/client/configuration.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/client/exceptions.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/client/models/page.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/client/models/threshold_settings.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/config.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/base.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/dashboard.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/dashboard_item.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/dataset_source.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/guardrails.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/integration.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/knowledge.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/mcp.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/model.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/model_provider.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/policy.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/source.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/tag.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/errors.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/agent/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/agent/flowise.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/context/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/context/context.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dashboard/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dashboard/dashboard.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dashboard/query.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dashboard_item/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dashboard_item/dashboard_item.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dataset/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dataset_source/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dataset_source/dataset_source.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/destination/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/guardrails/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/integration/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/knowledge/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/knowledge/knowledge.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/mcp/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/mcp/mcp.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/model/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/model/model.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/model_provider/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/model_provider/model_provider.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/policy/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/policy/policy.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/policy/query.py +0 -0
- {superwise_api-4.16.0/superwise_api/models/source → superwise_api-4.18.0/superwise_api/models/sentinel}/__init__.py +0 -0
- {superwise_api-4.16.0/superwise_api/models/tag → superwise_api-4.18.0/superwise_api/models/source}/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/source/source.py +0 -0
- {superwise_api-4.16.0/superwise_api/models/tool → superwise_api-4.18.0/superwise_api/models/tag}/__init__.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/tag/tag.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/tool/tool.py +0 -0
- {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: superwise-api
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.18.0
|
|
4
4
|
Summary: Superwise SDK for Python
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: OpenAPI,SuperwiseSDK
|
|
@@ -14,6 +14,8 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Requires-Dist: Pillow (>=10.0.0,<11.0.0)
|
|
16
16
|
Requires-Dist: aenum (>=3.1.11)
|
|
17
|
+
Requires-Dist: aiohttp (==3.13.5)
|
|
18
|
+
Requires-Dist: jsonschema (>=4.26.0,<5.0.0)
|
|
17
19
|
Requires-Dist: pydantic (>=2.10.4,<3.0.0)
|
|
18
20
|
Requires-Dist: pydantic-settings (>=2.7.0,<3.0.0)
|
|
19
21
|
Requires-Dist: python-dateutil (>=2.8.2)
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
packages = [{ include = "superwise_api", from = "." }]
|
|
3
|
-
version = "4.
|
|
3
|
+
version = "4.18.0"
|
|
4
4
|
include = ["superwise_api/client/py.typed"]
|
|
5
5
|
|
|
6
6
|
[tool.poetry.dependencies]
|
|
7
7
|
urllib3 = ">= 1.25.3"
|
|
8
|
+
aiohttp = "= 3.13.5"
|
|
8
9
|
python-dateutil = ">=2.8.2"
|
|
9
10
|
pydantic = "^2.10.4"
|
|
10
11
|
aenum = ">=3.1.11"
|
|
11
12
|
requests = "^2.32.0"
|
|
12
13
|
pydantic-settings = "^2.7.0"
|
|
13
14
|
Pillow = "^10.0.0"
|
|
15
|
+
jsonschema = "^4.26.0"
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
[tool.poetry.group.dev.dependencies]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import aiohttp
|
|
4
|
+
|
|
5
|
+
from superwise_api.auth import fetch_token_async
|
|
6
|
+
from superwise_api.config import Settings
|
|
7
|
+
from superwise_api.async_client.entities.dataset import AsyncDatasetAPI
|
|
8
|
+
from superwise_api.async_client.entities.sentinel import AsyncSentinelAPI
|
|
9
|
+
from superwise_api.async_client.transport import AiohttpTransport
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AsyncSuperwiseClient:
|
|
13
|
+
"""Async Superwise client.
|
|
14
|
+
|
|
15
|
+
Two usage patterns:
|
|
16
|
+
|
|
17
|
+
# Context manager:
|
|
18
|
+
async with AsyncSuperwiseClient(client_id="...", client_secret="...") as client:
|
|
19
|
+
await client.dataset.log_record(...)
|
|
20
|
+
|
|
21
|
+
# Caller-supplied session:
|
|
22
|
+
async with aiohttp.ClientSession() as session:
|
|
23
|
+
client = AsyncSuperwiseClient(..., session=session)
|
|
24
|
+
await client.dataset.log_record(...)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
client_id: str | None = None,
|
|
30
|
+
client_secret: str | None = None,
|
|
31
|
+
auth_host: str | None = None,
|
|
32
|
+
api_host: str | None = None,
|
|
33
|
+
use_hosted_auth: bool | None = None,
|
|
34
|
+
session: aiohttp.ClientSession | None = None,
|
|
35
|
+
) -> None:
|
|
36
|
+
settings = Settings(
|
|
37
|
+
**{
|
|
38
|
+
k: v
|
|
39
|
+
for k, v in dict(
|
|
40
|
+
client_id=client_id,
|
|
41
|
+
client_secret=client_secret,
|
|
42
|
+
auth_host=auth_host,
|
|
43
|
+
api_host=api_host,
|
|
44
|
+
use_hosted_auth=use_hosted_auth,
|
|
45
|
+
).items()
|
|
46
|
+
if v is not None
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
async def _token_factory() -> str:
|
|
51
|
+
return await fetch_token_async(settings.auth_url, settings.client_id, settings.client_secret)
|
|
52
|
+
|
|
53
|
+
self._transport = AiohttpTransport(
|
|
54
|
+
base_url=settings.api_host,
|
|
55
|
+
token_factory=_token_factory,
|
|
56
|
+
session=session,
|
|
57
|
+
)
|
|
58
|
+
self._dataset = AsyncDatasetAPI(self._transport)
|
|
59
|
+
self._sentinel = AsyncSentinelAPI(self._transport)
|
|
60
|
+
|
|
61
|
+
async def __aenter__(self) -> AsyncSuperwiseClient:
|
|
62
|
+
await self._transport.__aenter__()
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
async def __aexit__(self, *_: object) -> None:
|
|
66
|
+
await self._transport.__aexit__()
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def dataset(self) -> AsyncDatasetAPI:
|
|
70
|
+
return self._dataset
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def sentinel(self) -> AsyncSentinelAPI:
|
|
74
|
+
return self._sentinel
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from superwise_api.request_builders.dataset_request_builder import (
|
|
4
|
+
parse_log_batch_response,
|
|
5
|
+
parse_log_record_response,
|
|
6
|
+
prepare_log_batch_request,
|
|
7
|
+
prepare_log_record_request,
|
|
8
|
+
)
|
|
9
|
+
from superwise_api.async_client.transport import AsyncTransport
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AsyncDatasetAPI:
|
|
13
|
+
def __init__(self, transport: AsyncTransport) -> None:
|
|
14
|
+
self._transport = transport
|
|
15
|
+
|
|
16
|
+
async def log_record(
|
|
17
|
+
self,
|
|
18
|
+
dataset_id: str,
|
|
19
|
+
data: dict,
|
|
20
|
+
images: dict[str, tuple[bytes, str]] | None = None,
|
|
21
|
+
) -> None:
|
|
22
|
+
req = prepare_log_record_request(dataset_id, data, images)
|
|
23
|
+
status, body = await self._transport.execute(req)
|
|
24
|
+
return parse_log_record_response(status, body)
|
|
25
|
+
|
|
26
|
+
async def log_batch(self, dataset_id: str, records: list[dict]) -> dict | None:
|
|
27
|
+
req = prepare_log_batch_request(dataset_id, records)
|
|
28
|
+
status, body = await self._transport.execute(req)
|
|
29
|
+
return parse_log_batch_response(status, body)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from superwise_api.async_client.transport import AsyncTransport
|
|
6
|
+
from superwise_api.request_builders.sentinel_request_builder import (
|
|
7
|
+
parse_delete_sentinel_response,
|
|
8
|
+
parse_sentinel_response,
|
|
9
|
+
parse_sentinels_response,
|
|
10
|
+
prepare_create_sentinel_request,
|
|
11
|
+
prepare_delete_sentinel_request,
|
|
12
|
+
prepare_get_sentinel_request,
|
|
13
|
+
prepare_get_sentinels_request,
|
|
14
|
+
prepare_heartbeat_sentinel_request,
|
|
15
|
+
prepare_search_sentinels_request,
|
|
16
|
+
prepare_update_sentinel_request,
|
|
17
|
+
)
|
|
18
|
+
from superwise_api.models.sentinel.sentinel import Sentinel
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AsyncSentinelAPI:
|
|
22
|
+
def __init__(self, transport: AsyncTransport) -> None:
|
|
23
|
+
self._transport = transport
|
|
24
|
+
|
|
25
|
+
async def create(
|
|
26
|
+
self,
|
|
27
|
+
name: str | None = None,
|
|
28
|
+
description: str | None = None,
|
|
29
|
+
location: str | None = None,
|
|
30
|
+
local_guardrail_config: dict[str, Any] | None = None,
|
|
31
|
+
remote_guardrail_versions: list[str] | None = None,
|
|
32
|
+
) -> Sentinel:
|
|
33
|
+
req = prepare_create_sentinel_request(
|
|
34
|
+
name, description, location, local_guardrail_config, remote_guardrail_versions
|
|
35
|
+
)
|
|
36
|
+
status, body = await self._transport.execute(req)
|
|
37
|
+
return parse_sentinel_response(status, body)
|
|
38
|
+
|
|
39
|
+
async def get(self, page: int | None = None, size: int | None = None) -> list[Sentinel]:
|
|
40
|
+
req = prepare_get_sentinels_request(page, size)
|
|
41
|
+
status, body = await self._transport.execute(req)
|
|
42
|
+
return parse_sentinels_response(status, body)
|
|
43
|
+
|
|
44
|
+
async def get_by_id(self, sentinel_id: str) -> Sentinel:
|
|
45
|
+
req = prepare_get_sentinel_request(sentinel_id)
|
|
46
|
+
status, body = await self._transport.execute(req)
|
|
47
|
+
return parse_sentinel_response(status, body)
|
|
48
|
+
|
|
49
|
+
async def search(
|
|
50
|
+
self,
|
|
51
|
+
filters: list[Any] | None = None,
|
|
52
|
+
search: list[str] | str | None = None,
|
|
53
|
+
sort_by: str = "updated_at",
|
|
54
|
+
sort_direction: str = "desc",
|
|
55
|
+
) -> list[Sentinel]:
|
|
56
|
+
req = prepare_search_sentinels_request(filters, search, sort_by, sort_direction)
|
|
57
|
+
status, body = await self._transport.execute(req)
|
|
58
|
+
return parse_sentinels_response(status, body)
|
|
59
|
+
|
|
60
|
+
async def update(
|
|
61
|
+
self,
|
|
62
|
+
sentinel_id: str,
|
|
63
|
+
name: str | None = None,
|
|
64
|
+
description: str | None = None,
|
|
65
|
+
location: str | None = None,
|
|
66
|
+
local_guardrail_config: dict[str, Any] | None = None,
|
|
67
|
+
remote_guardrail_versions: list[str] | None = None,
|
|
68
|
+
) -> Sentinel:
|
|
69
|
+
req = prepare_update_sentinel_request(
|
|
70
|
+
sentinel_id, name, description, location, local_guardrail_config, remote_guardrail_versions
|
|
71
|
+
)
|
|
72
|
+
status, body = await self._transport.execute(req)
|
|
73
|
+
return parse_sentinel_response(status, body)
|
|
74
|
+
|
|
75
|
+
async def heartbeat(self, sentinel_id: str) -> Sentinel:
|
|
76
|
+
req = prepare_heartbeat_sentinel_request(sentinel_id)
|
|
77
|
+
status, body = await self._transport.execute(req)
|
|
78
|
+
return parse_sentinel_response(status, body)
|
|
79
|
+
|
|
80
|
+
async def delete(self, sentinel_id: str) -> None:
|
|
81
|
+
req = prepare_delete_sentinel_request(sentinel_id)
|
|
82
|
+
status, body = await self._transport.execute(req)
|
|
83
|
+
return parse_delete_sentinel_response(status, body)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
|
+
from typing import Protocol
|
|
6
|
+
|
|
7
|
+
import aiohttp
|
|
8
|
+
|
|
9
|
+
from superwise_api.request_builders.requests import PreparedRequest, MultipartRequest
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AsyncTransport(Protocol):
|
|
13
|
+
async def execute(self, req: PreparedRequest) -> tuple[int, bytes]: ...
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AiohttpTransport:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
base_url: str,
|
|
20
|
+
token_factory: Callable[[], Awaitable[str]],
|
|
21
|
+
session: aiohttp.ClientSession | None = None,
|
|
22
|
+
) -> None:
|
|
23
|
+
self._base_url = base_url.rstrip("/")
|
|
24
|
+
self._token_factory = token_factory
|
|
25
|
+
self._token: str | None = None
|
|
26
|
+
self._token_lock = asyncio.Lock()
|
|
27
|
+
self._session = session
|
|
28
|
+
self._owns_session = session is None
|
|
29
|
+
|
|
30
|
+
async def close(self) -> None:
|
|
31
|
+
if self._owns_session and self._session is not None:
|
|
32
|
+
await self._session.__aexit__(None, None, None)
|
|
33
|
+
self._session = None
|
|
34
|
+
self._owns_session = False
|
|
35
|
+
|
|
36
|
+
async def __aenter__(self) -> AiohttpTransport:
|
|
37
|
+
if self._session is None:
|
|
38
|
+
self._session = await aiohttp.ClientSession().__aenter__()
|
|
39
|
+
self._owns_session = True
|
|
40
|
+
return self
|
|
41
|
+
|
|
42
|
+
async def __aexit__(self, *_: object) -> None:
|
|
43
|
+
await self.close()
|
|
44
|
+
|
|
45
|
+
async def execute(self, req: PreparedRequest) -> tuple[int, bytes]:
|
|
46
|
+
if self._token is None:
|
|
47
|
+
async with self._token_lock:
|
|
48
|
+
if self._token is None:
|
|
49
|
+
self._token = await self._token_factory()
|
|
50
|
+
status, body = await self._execute(req)
|
|
51
|
+
if status == 401:
|
|
52
|
+
stale_token = self._token
|
|
53
|
+
async with self._token_lock:
|
|
54
|
+
if self._token == stale_token:
|
|
55
|
+
self._token = await self._token_factory()
|
|
56
|
+
status, body = await self._execute(req)
|
|
57
|
+
return status, body
|
|
58
|
+
|
|
59
|
+
async def _execute(self, req: PreparedRequest) -> tuple[int, bytes]:
|
|
60
|
+
url = self._base_url + req.path
|
|
61
|
+
headers = {**req.headers, "Authorization": f"Bearer {self._token}"}
|
|
62
|
+
body_kwargs = {"data": self._build_form(req)} if req.is_multipart() else req.body_kwargs()
|
|
63
|
+
async with self._session.request(req.method, url, headers=headers, **body_kwargs) as resp:
|
|
64
|
+
return resp.status, await resp.read()
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def _build_form(req: MultipartRequest) -> aiohttp.FormData:
|
|
68
|
+
form = aiohttp.FormData()
|
|
69
|
+
for f in req.fields:
|
|
70
|
+
kwargs = {}
|
|
71
|
+
if f.content_type is not None:
|
|
72
|
+
kwargs["content_type"] = f.content_type
|
|
73
|
+
if f.filename is not None:
|
|
74
|
+
kwargs["filename"] = f.filename
|
|
75
|
+
form.add_field(f.name, f.data, **kwargs)
|
|
76
|
+
return form
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
import aiohttp
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from superwise_api.client.exceptions import UnauthorizedException
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class TokenRequest:
|
|
11
|
+
url: str
|
|
12
|
+
headers: dict[str, str]
|
|
13
|
+
body: dict
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def prepare_token_request(auth_url: str, client_id: str, client_secret: str) -> TokenRequest:
|
|
17
|
+
return TokenRequest(
|
|
18
|
+
url=auth_url,
|
|
19
|
+
headers={"accept": "application/json", "content-type": "application/json"},
|
|
20
|
+
body={"clientId": client_id, "secret": client_secret},
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def parse_token_response(status_code: int, body: bytes) -> str:
|
|
25
|
+
if status_code != 200:
|
|
26
|
+
raise UnauthorizedException(status=status_code, reason=body.decode("utf-8") if body else "")
|
|
27
|
+
return json.loads(body)["accessToken"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def fetch_token(auth_url: str, client_id: str, client_secret: str) -> str:
|
|
31
|
+
req = prepare_token_request(auth_url, client_id, client_secret)
|
|
32
|
+
resp = requests.post(req.url, json=req.body, headers=req.headers)
|
|
33
|
+
return parse_token_response(resp.status_code, resp.content)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def fetch_token_async(auth_url: str, client_id: str, client_secret: str) -> str:
|
|
37
|
+
req = prepare_token_request(auth_url, client_id, client_secret)
|
|
38
|
+
async with aiohttp.ClientSession() as session:
|
|
39
|
+
async with session.post(req.url, json=req.body, headers=req.headers) as resp:
|
|
40
|
+
return parse_token_response(resp.status, await resp.read())
|
|
@@ -7,7 +7,7 @@ import re
|
|
|
7
7
|
import tempfile
|
|
8
8
|
import uuid
|
|
9
9
|
from multiprocessing.pool import ThreadPool
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
from urllib.parse import quote
|
|
12
12
|
|
|
13
13
|
from dateutil.parser import parse
|
|
@@ -774,7 +774,7 @@ class ApiClient:
|
|
|
774
774
|
**kwargs,
|
|
775
775
|
)
|
|
776
776
|
|
|
777
|
-
def put(self, resource_path: str, data:
|
|
777
|
+
def put(self, resource_path: str, data: dict | None = None, **kwargs) -> BaseModel:
|
|
778
778
|
_params = locals()
|
|
779
779
|
|
|
780
780
|
_all_params = ["async_req", "_request_auth", "_content_type", "_headers", "response_types_map"]
|
|
@@ -860,11 +860,19 @@ class ApiClient:
|
|
|
860
860
|
)
|
|
861
861
|
|
|
862
862
|
def post(
|
|
863
|
-
self, resource_path: str, data:
|
|
863
|
+
self, resource_path: str, data: dict | None = None, query_params: dict | None = None, **kwargs
|
|
864
864
|
) -> BaseModel:
|
|
865
865
|
_params = locals()
|
|
866
866
|
|
|
867
|
-
_all_params = [
|
|
867
|
+
_all_params = [
|
|
868
|
+
"files",
|
|
869
|
+
"async_req",
|
|
870
|
+
"_request_auth",
|
|
871
|
+
"_content_type",
|
|
872
|
+
"_headers",
|
|
873
|
+
"response_types_map",
|
|
874
|
+
"post_params",
|
|
875
|
+
]
|
|
868
876
|
if "model_name" in kwargs:
|
|
869
877
|
_all_params.append("model_name")
|
|
870
878
|
|
|
@@ -884,18 +892,21 @@ class ApiClient:
|
|
|
884
892
|
# process the header parameters
|
|
885
893
|
_header_params = dict(_params.get("_headers", {}))
|
|
886
894
|
# process the form parameters
|
|
887
|
-
_form_params = []
|
|
895
|
+
_form_params = _params.get("post_params", [])
|
|
888
896
|
_files = kwargs.get("files", {})
|
|
889
897
|
# process the body parameter
|
|
890
898
|
_body_params = None
|
|
891
|
-
if data is not None:
|
|
899
|
+
if data is not None and not _form_params:
|
|
892
900
|
_body_params = data
|
|
893
901
|
|
|
894
902
|
# set the HTTP header `Accept`
|
|
895
903
|
_header_params["Accept"] = self.select_header_accept(["application/json"])
|
|
896
904
|
|
|
897
905
|
# set the HTTP header `Content-Type`
|
|
898
|
-
|
|
906
|
+
if _form_params:
|
|
907
|
+
_content_types_list = "multipart/form-data"
|
|
908
|
+
else:
|
|
909
|
+
_content_types_list = _params.get("_content_type", self.select_header_content_type(["application/json"]))
|
|
899
910
|
if _content_types_list:
|
|
900
911
|
_header_params["Content-Type"] = _content_types_list
|
|
901
912
|
|
|
@@ -924,7 +935,7 @@ class ApiClient:
|
|
|
924
935
|
)
|
|
925
936
|
|
|
926
937
|
def delete(
|
|
927
|
-
self, resource_path: str, model_name: str, entity_id: str, query_params:
|
|
938
|
+
self, resource_path: str, model_name: str, entity_id: str, query_params: dict | None = None, **kwargs
|
|
928
939
|
) -> BaseModel:
|
|
929
940
|
_params = locals()
|
|
930
941
|
|
|
@@ -1151,14 +1162,16 @@ class ApiClient:
|
|
|
1151
1162
|
self,
|
|
1152
1163
|
resource_path: str,
|
|
1153
1164
|
response_model,
|
|
1154
|
-
data:
|
|
1165
|
+
data: dict | None = None,
|
|
1166
|
+
post_params: list | None = None,
|
|
1155
1167
|
**kwargs,
|
|
1156
1168
|
):
|
|
1157
1169
|
"""Performs a streaming POST request and yields parsed response objects.
|
|
1158
1170
|
|
|
1159
1171
|
:param resource_path: Path to method endpoint.
|
|
1160
1172
|
:param response_model: Model class to deserialize each line to.
|
|
1161
|
-
:param data: Request body data.
|
|
1173
|
+
:param data: Request body data (JSON).
|
|
1174
|
+
:param post_params: Multipart form-data fields (list of tuples). Mutually exclusive with data.
|
|
1162
1175
|
:param kwargs: Additional arguments (_headers, _request_timeout, _request_auth).
|
|
1163
1176
|
:yields: Parsed response_model instances.
|
|
1164
1177
|
:raises StreamingError: If an error event is received in the stream.
|
|
@@ -1175,7 +1188,7 @@ class ApiClient:
|
|
|
1175
1188
|
|
|
1176
1189
|
# Set content type and accept headers
|
|
1177
1190
|
_header_params["Accept"] = "application/x-ndjson"
|
|
1178
|
-
_header_params["Content-Type"] = "application/json"
|
|
1191
|
+
_header_params["Content-Type"] = "multipart/form-data" if post_params else "application/json"
|
|
1179
1192
|
|
|
1180
1193
|
# Handle authentication
|
|
1181
1194
|
_request_auth = _params.get("_request_auth")
|
|
@@ -1196,6 +1209,7 @@ class ApiClient:
|
|
|
1196
1209
|
url,
|
|
1197
1210
|
headers=_header_params,
|
|
1198
1211
|
body=data,
|
|
1212
|
+
post_params=post_params,
|
|
1199
1213
|
_request_timeout=_params.get("_request_timeout"),
|
|
1200
1214
|
)
|
|
1201
1215
|
|
|
@@ -404,6 +404,7 @@ class RESTClientObject:
|
|
|
404
404
|
url,
|
|
405
405
|
headers=None,
|
|
406
406
|
body=None,
|
|
407
|
+
post_params=None,
|
|
407
408
|
_request_timeout=None,
|
|
408
409
|
):
|
|
409
410
|
"""Perform a streaming POST request.
|
|
@@ -411,6 +412,7 @@ class RESTClientObject:
|
|
|
411
412
|
:param url: http request url
|
|
412
413
|
:param headers: http request headers
|
|
413
414
|
:param body: request json body
|
|
415
|
+
:param post_params: multipart/form-data fields (list of tuples)
|
|
414
416
|
:param _request_timeout: timeout setting for this request
|
|
415
417
|
:return: StreamingRESTResponse object for iterating over lines
|
|
416
418
|
"""
|
|
@@ -424,18 +426,30 @@ class RESTClientObject:
|
|
|
424
426
|
timeout = urllib3.Timeout(connect=_request_timeout[0], read=_request_timeout[1])
|
|
425
427
|
|
|
426
428
|
try:
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
429
|
+
if post_params:
|
|
430
|
+
# Remove Content-Type so urllib3 sets it with the multipart boundary
|
|
431
|
+
headers = {k: v for k, v in headers.items() if k != "Content-Type"}
|
|
432
|
+
r = self.pool_manager.request(
|
|
433
|
+
"POST",
|
|
434
|
+
url,
|
|
435
|
+
fields=post_params,
|
|
436
|
+
encode_multipart=True,
|
|
437
|
+
preload_content=False,
|
|
438
|
+
timeout=timeout,
|
|
439
|
+
headers=headers,
|
|
440
|
+
)
|
|
441
|
+
else:
|
|
442
|
+
request_body = None
|
|
443
|
+
if body is not None:
|
|
444
|
+
request_body = json.dumps(body)
|
|
445
|
+
r = self.pool_manager.request(
|
|
446
|
+
"POST",
|
|
447
|
+
url,
|
|
448
|
+
body=request_body,
|
|
449
|
+
preload_content=False,
|
|
450
|
+
timeout=timeout,
|
|
451
|
+
headers=headers,
|
|
452
|
+
)
|
|
439
453
|
except urllib3.exceptions.SSLError as e:
|
|
440
454
|
msg = "{0}\n{1}".format(type(e).__name__, str(e))
|
|
441
455
|
raise ApiException(status=0, reason=msg)
|