superwise-api 4.17.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.17.0 → superwise_api-4.18.0}/PKG-INFO +2 -1
- {superwise_api-4.17.0 → superwise_api-4.18.0}/pyproject.toml +2 -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.17.0 → superwise_api-4.18.0}/superwise_api/entities/dataset.py +18 -42
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/destination.py +12 -3
- superwise_api-4.18.0/superwise_api/entities/sentinel.py +141 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/agent/agent.py +16 -12
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dataset/dataset_schema.py +11 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/destination/destination.py +35 -11
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/guardrails/guardrails.py +16 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/integration/integration.py +1 -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.17.0 → superwise_api-4.18.0}/superwise_api/superwise_client.py +8 -27
- {superwise_api-4.17.0 → superwise_api-4.18.0}/README.md +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/client/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/client/api_client.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/client/api_response.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/client/configuration.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/client/exceptions.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/client/models/page.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/client/models/threshold_settings.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/client/rest.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/config.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/agent.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/base.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/dashboard.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/dashboard_item.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/dataset_source.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/guardrails.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/integration.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/knowledge.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/mcp.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/model.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/model_provider.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/policy.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/source.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/tag.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/errors.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/agent/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/agent/feedback.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/agent/flowise.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/agent/playground.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/context/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/context/context.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dashboard/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dashboard/dashboard.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dashboard/query.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dashboard_item/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dashboard_item/dashboard_item.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dataset/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dataset/dataset.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dataset_source/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dataset_source/dataset_source.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/destination/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/guardrails/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/integration/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/knowledge/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/knowledge/knowledge.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/mcp/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/mcp/mcp.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/model/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/model/model.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/model_provider/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/model_provider/model_provider.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/policy/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/policy/policy.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/policy/query.py +0 -0
- {superwise_api-4.17.0/superwise_api/models/source → superwise_api-4.18.0/superwise_api/models/sentinel}/__init__.py +0 -0
- {superwise_api-4.17.0/superwise_api/models/tag → superwise_api-4.18.0/superwise_api/models/source}/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/source/source.py +0 -0
- {superwise_api-4.17.0/superwise_api/models/tool → superwise_api-4.18.0/superwise_api/models/tag}/__init__.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/tag/tag.py +0 -0
- {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/tool/tool.py +0 -0
- {superwise_api-4.17.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,7 @@ 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)
|
|
17
18
|
Requires-Dist: jsonschema (>=4.26.0,<5.0.0)
|
|
18
19
|
Requires-Dist: pydantic (>=2.10.4,<3.0.0)
|
|
19
20
|
Requires-Dist: pydantic-settings (>=2.7.0,<3.0.0)
|
|
@@ -1,10 +1,11 @@
|
|
|
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"
|
|
@@ -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())
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import json
|
|
2
1
|
from typing import Optional
|
|
3
2
|
|
|
4
3
|
from pydantic import conint
|
|
5
4
|
|
|
6
5
|
from superwise_api.client.models.page import Page
|
|
7
6
|
from superwise_api.entities.base import BaseApi, CoreEntitiyApi
|
|
7
|
+
from superwise_api.request_builders.dataset_request_builder import (
|
|
8
|
+
prepare_log_batch_request,
|
|
9
|
+
prepare_log_record_request,
|
|
10
|
+
)
|
|
11
|
+
from superwise_api.request_builders.requests import JsonRequest
|
|
8
12
|
from superwise_api.models.dataset.dataset import Dataset, NaturalLanguageQueryResponse, SqlQueryResponse
|
|
9
|
-
from superwise_api.models.dataset.dataset_schema import RecordLogMessage
|
|
10
13
|
from superwise_api.models.dataset_source.dataset_source import DatasetSource
|
|
11
14
|
from superwise_api.models.dataset_source.dataset_source import IngestType
|
|
12
15
|
|
|
@@ -140,7 +143,7 @@ class DatasetApi(CoreEntitiyApi):
|
|
|
140
143
|
dataset_id (str): The id of the dataset to update.
|
|
141
144
|
name (str, optional): The new name of the dataset.
|
|
142
145
|
description (str, optional): The new description of the dataset.
|
|
143
|
-
schema (dict, optional): The
|
|
146
|
+
schema (dict, optional): The schema patch. key_field cannot be changed after creation.
|
|
144
147
|
**kwargs: Arbitrary keyword arguments.
|
|
145
148
|
|
|
146
149
|
Returns:
|
|
@@ -230,37 +233,15 @@ class DatasetApi(CoreEntitiyApi):
|
|
|
230
233
|
images: dict[str, tuple[bytes, str]] | None = None,
|
|
231
234
|
**kwargs,
|
|
232
235
|
):
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
e.g. ``{"photo": (raw_bytes, "image/jpeg")}``.
|
|
241
|
-
"""
|
|
242
|
-
if images:
|
|
243
|
-
return self._log_record_with_images(dataset_id, data, images, **kwargs)
|
|
244
|
-
return self.api_client.post(
|
|
245
|
-
resource_path=f"{self._resource_path}/{dataset_id}/log",
|
|
246
|
-
data=RecordLogMessage(record=data).model_dump(),
|
|
247
|
-
**kwargs,
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
def _log_record_with_images(
|
|
251
|
-
self,
|
|
252
|
-
dataset_id: str,
|
|
253
|
-
data: dict,
|
|
254
|
-
images: dict[str, tuple[bytes, str]],
|
|
255
|
-
**kwargs,
|
|
256
|
-
):
|
|
257
|
-
"""Send a multipart/form-data log request with embedded images."""
|
|
258
|
-
post_params = [("record", json.dumps(data))]
|
|
259
|
-
for field_name, (image_bytes, content_type) in images.items():
|
|
260
|
-
post_params.append((field_name, (field_name, image_bytes, content_type)))
|
|
261
|
-
|
|
236
|
+
req = prepare_log_record_request(dataset_id, data, images)
|
|
237
|
+
if isinstance(req, JsonRequest):
|
|
238
|
+
return self.api_client.post(resource_path=req.path, data=req.body, **kwargs)
|
|
239
|
+
post_params = [
|
|
240
|
+
(f.name, (f.filename or f.name, f.data, f.content_type)) if f.content_type else (f.name, f.data)
|
|
241
|
+
for f in req.fields
|
|
242
|
+
]
|
|
262
243
|
return self.api_client.call_api(
|
|
263
|
-
|
|
244
|
+
req.path,
|
|
264
245
|
"POST",
|
|
265
246
|
header_params={"Content-Type": "multipart/form-data"},
|
|
266
247
|
post_params=post_params,
|
|
@@ -271,17 +252,12 @@ class DatasetApi(CoreEntitiyApi):
|
|
|
271
252
|
)
|
|
272
253
|
|
|
273
254
|
def log_batch_to_dataset(self, dataset_id: str, records: list[dict]) -> dict:
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
Each item in ``records`` is the inner record dict (field names → values), e.g. for ``image_link``
|
|
277
|
-
fields use ``{"type": "image_link", "url": "https://..."}`` per schema.
|
|
278
|
-
"""
|
|
279
|
-
body = [RecordLogMessage(record=r).model_dump() for r in records]
|
|
255
|
+
req = prepare_log_batch_request(dataset_id, records)
|
|
280
256
|
return self.api_client.call_api(
|
|
281
|
-
|
|
257
|
+
req.path,
|
|
282
258
|
"POST",
|
|
283
259
|
header_params={"x-dataset-id": dataset_id},
|
|
284
|
-
body=body,
|
|
260
|
+
body=req.body,
|
|
285
261
|
response_types_map={"202": object},
|
|
286
262
|
auth_settings=["implicit"],
|
|
287
263
|
_return_http_data_only=True,
|
|
@@ -308,7 +284,7 @@ class DatasetApi(CoreEntitiyApi):
|
|
|
308
284
|
``(content_type, image_bytes)`` tuple,
|
|
309
285
|
e.g. ``("image/jpeg", b"\\xff\\xd8...")``.
|
|
310
286
|
"""
|
|
311
|
-
payload = {"
|
|
287
|
+
payload = {"image_id": image_id, "thumbnail_size": "small" if thumbnail else None}
|
|
312
288
|
|
|
313
289
|
response = self.api_client.call_api(
|
|
314
290
|
f"{self._resource_path}/get-media",
|
|
@@ -7,6 +7,7 @@ from superwise_api.models.destination.destination import (
|
|
|
7
7
|
TeamsDestinationTestRequest,
|
|
8
8
|
SlackDestinationTestRequest,
|
|
9
9
|
EmailDestinationTestRequest,
|
|
10
|
+
WebhookDestinationTestRequest,
|
|
10
11
|
DestinationParams,
|
|
11
12
|
DestinationType,
|
|
12
13
|
)
|
|
@@ -66,6 +67,7 @@ class DestinationApi(BaseApi):
|
|
|
66
67
|
integration_type (str): The type of the integration.
|
|
67
68
|
params (DestinationParams): The parameters of the destination.
|
|
68
69
|
integration_id (str | None): The id of the integration. Required for Slack destinations.
|
|
70
|
+
Omit for Teams, Email, and Webhook (self-serve integration types).
|
|
69
71
|
**kwargs: Arbitrary keyword arguments.
|
|
70
72
|
|
|
71
73
|
Returns:
|
|
@@ -129,14 +131,21 @@ class DestinationApi(BaseApi):
|
|
|
129
131
|
|
|
130
132
|
def test_connection(
|
|
131
133
|
self,
|
|
132
|
-
request:
|
|
134
|
+
request: (
|
|
135
|
+
TeamsDestinationTestRequest
|
|
136
|
+
| SlackDestinationTestRequest
|
|
137
|
+
| EmailDestinationTestRequest
|
|
138
|
+
| WebhookDestinationTestRequest
|
|
139
|
+
),
|
|
133
140
|
**kwargs,
|
|
134
141
|
) -> None:
|
|
135
142
|
"""
|
|
136
|
-
Test a
|
|
143
|
+
Test a destination by sending a test notification.
|
|
137
144
|
|
|
138
145
|
Args:
|
|
139
|
-
request
|
|
146
|
+
request: The test connection request payload. One of
|
|
147
|
+
``TeamsDestinationTestRequest``, ``SlackDestinationTestRequest``,
|
|
148
|
+
``EmailDestinationTestRequest``, or ``WebhookDestinationTestRequest``.
|
|
140
149
|
**kwargs: Arbitrary keyword arguments.
|
|
141
150
|
"""
|
|
142
151
|
self.api_client.post(
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from superwise_api.client.models.page import Page
|
|
6
|
+
from superwise_api.entities.base import BaseApi
|
|
7
|
+
from superwise_api.request_builders.sentinel_request_builder import (
|
|
8
|
+
prepare_delete_sentinel_request,
|
|
9
|
+
prepare_get_sentinel_request,
|
|
10
|
+
prepare_get_sentinels_request,
|
|
11
|
+
prepare_heartbeat_sentinel_request,
|
|
12
|
+
prepare_search_sentinels_request,
|
|
13
|
+
prepare_create_sentinel_request,
|
|
14
|
+
prepare_update_sentinel_request,
|
|
15
|
+
)
|
|
16
|
+
from superwise_api.models.sentinel.sentinel import Sentinel
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SentinelApi(BaseApi):
|
|
20
|
+
_model_name = "sentinel"
|
|
21
|
+
_resource_path = "/v1/sentinels"
|
|
22
|
+
_model_class = Sentinel
|
|
23
|
+
|
|
24
|
+
def create(
|
|
25
|
+
self,
|
|
26
|
+
name: str | None = None,
|
|
27
|
+
description: str | None = None,
|
|
28
|
+
location: str | None = None,
|
|
29
|
+
local_guardrail_config: dict[str, Any] | None = None,
|
|
30
|
+
remote_guardrail_versions: list[str] | None = None,
|
|
31
|
+
) -> Sentinel:
|
|
32
|
+
req = prepare_create_sentinel_request(
|
|
33
|
+
name, description, location, local_guardrail_config, remote_guardrail_versions
|
|
34
|
+
)
|
|
35
|
+
return self.api_client.call_api(
|
|
36
|
+
req.path,
|
|
37
|
+
req.method,
|
|
38
|
+
header_params=req.headers,
|
|
39
|
+
body=req.body,
|
|
40
|
+
response_types_map={"201": Sentinel, "401": None, "422": None},
|
|
41
|
+
auth_settings=["implicit"],
|
|
42
|
+
_return_http_data_only=True,
|
|
43
|
+
_preload_content=True,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def get(self, page: int | None = None, size: int | None = None) -> Page:
|
|
47
|
+
req = prepare_get_sentinels_request(page, size)
|
|
48
|
+
return self.api_client.call_api(
|
|
49
|
+
req.path,
|
|
50
|
+
req.method,
|
|
51
|
+
header_params=req.headers,
|
|
52
|
+
query_params=req.query_params,
|
|
53
|
+
body=None,
|
|
54
|
+
response_types_map={"200": Page.set_model(Sentinel), "422": None, "500": None},
|
|
55
|
+
auth_settings=["implicit"],
|
|
56
|
+
_return_http_data_only=True,
|
|
57
|
+
_preload_content=True,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def get_by_id(self, sentinel_id: str, **kwargs) -> Sentinel:
|
|
61
|
+
req = prepare_get_sentinel_request(sentinel_id)
|
|
62
|
+
return self.api_client.call_api(
|
|
63
|
+
req.path,
|
|
64
|
+
req.method,
|
|
65
|
+
header_params=req.headers,
|
|
66
|
+
body=None,
|
|
67
|
+
response_types_map={"200": Sentinel, "404": None, "422": None, "500": None},
|
|
68
|
+
auth_settings=["implicit"],
|
|
69
|
+
_return_http_data_only=True,
|
|
70
|
+
_preload_content=True,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
@BaseApi.raise_exception
|
|
74
|
+
def search(
|
|
75
|
+
self,
|
|
76
|
+
filters: list[Any] | None = None,
|
|
77
|
+
search: list[str] | str | None = None,
|
|
78
|
+
sort_by: str = "updated_at",
|
|
79
|
+
sort_direction: str = "desc",
|
|
80
|
+
) -> Page:
|
|
81
|
+
req = prepare_search_sentinels_request(filters, search, sort_by, sort_direction)
|
|
82
|
+
return self.api_client.call_api(
|
|
83
|
+
req.path,
|
|
84
|
+
req.method,
|
|
85
|
+
header_params=req.headers,
|
|
86
|
+
body=req.body,
|
|
87
|
+
response_types_map={"200": Page.set_model(Sentinel), "422": None, "500": None},
|
|
88
|
+
auth_settings=["implicit"],
|
|
89
|
+
_return_http_data_only=True,
|
|
90
|
+
_preload_content=True,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def update(
|
|
94
|
+
self,
|
|
95
|
+
sentinel_id: str,
|
|
96
|
+
name: str | None = None,
|
|
97
|
+
description: str | None = None,
|
|
98
|
+
location: str | None = None,
|
|
99
|
+
local_guardrail_config: dict[str, Any] | None = None,
|
|
100
|
+
remote_guardrail_versions: list[str] | None = None,
|
|
101
|
+
) -> Sentinel:
|
|
102
|
+
req = prepare_update_sentinel_request(
|
|
103
|
+
sentinel_id, name, description, location, local_guardrail_config, remote_guardrail_versions
|
|
104
|
+
)
|
|
105
|
+
return self.api_client.call_api(
|
|
106
|
+
req.path,
|
|
107
|
+
req.method,
|
|
108
|
+
header_params=req.headers,
|
|
109
|
+
body=req.body,
|
|
110
|
+
response_types_map={"200": Sentinel, "404": None, "422": None, "500": None},
|
|
111
|
+
auth_settings=["implicit"],
|
|
112
|
+
_return_http_data_only=True,
|
|
113
|
+
_preload_content=True,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
@BaseApi.raise_exception
|
|
117
|
+
def heartbeat(self, sentinel_id: str) -> Sentinel:
|
|
118
|
+
req = prepare_heartbeat_sentinel_request(sentinel_id)
|
|
119
|
+
return self.api_client.call_api(
|
|
120
|
+
req.path,
|
|
121
|
+
req.method,
|
|
122
|
+
header_params=req.headers,
|
|
123
|
+
body=req.body,
|
|
124
|
+
response_types_map={"200": Sentinel, "404": None, "500": None},
|
|
125
|
+
auth_settings=["implicit"],
|
|
126
|
+
_return_http_data_only=True,
|
|
127
|
+
_preload_content=True,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def delete(self, sentinel_id: str, **kwargs) -> None:
|
|
131
|
+
req = prepare_delete_sentinel_request(sentinel_id)
|
|
132
|
+
return self.api_client.call_api(
|
|
133
|
+
req.path,
|
|
134
|
+
req.method,
|
|
135
|
+
header_params=req.headers,
|
|
136
|
+
body=None,
|
|
137
|
+
response_types_map={},
|
|
138
|
+
auth_settings=["implicit"],
|
|
139
|
+
_return_http_data_only=True,
|
|
140
|
+
_preload_content=True,
|
|
141
|
+
)
|
|
@@ -39,34 +39,38 @@ class OpenAIModelVersion(str, Enum):
|
|
|
39
39
|
GPT_4_1 = "gpt-4.1"
|
|
40
40
|
GPT_4_1_MINI = "gpt-4.1-mini"
|
|
41
41
|
GPT_4_1_NANO = "gpt-4.1-nano"
|
|
42
|
-
CHATGPT_4O_LATEST = "chatgpt-4o-latest"
|
|
43
42
|
O1 = "o1"
|
|
44
|
-
O3_MINI = "o3-mini"
|
|
45
43
|
O3 = "o3"
|
|
46
|
-
|
|
47
|
-
GPT_5_NANO = "gpt-5-nano"
|
|
44
|
+
O3_MINI = "o3-mini"
|
|
48
45
|
GPT_5 = "gpt-5"
|
|
49
46
|
GPT_5_MINI = "gpt-5-mini"
|
|
47
|
+
GPT_5_NANO = "gpt-5-nano"
|
|
48
|
+
GPT_5_1 = "gpt-5.1"
|
|
49
|
+
GPT_5_2 = "gpt-5.2"
|
|
50
|
+
GPT_5_4 = "gpt-5.4"
|
|
51
|
+
GPT_5_4_MINI = "gpt-5.4-mini"
|
|
52
|
+
GPT_5_4_NANO = "gpt-5.4-nano"
|
|
53
|
+
GPT_5_5 = "gpt-5.5"
|
|
50
54
|
|
|
51
55
|
|
|
52
56
|
class GoogleModelVersion(str, Enum):
|
|
53
|
-
GEMINI_2_0_FLASH = "models/gemini-2.0-flash"
|
|
54
|
-
GEMINI_2_0_FLASH_LITE = "models/gemini-2.0-flash-lite"
|
|
55
|
-
GEMINI_2_0_FLASH_EXP = "models/gemini-2.0-flash-exp"
|
|
56
|
-
GEMINI_2_0_FLASH_THINKING_EXP = "models/gemini-2.0-flash-thinking-exp"
|
|
57
|
-
GEMINI_2_5_FLASH = "models/gemini-2.5-flash"
|
|
58
57
|
GEMINI_2_5_PRO = "models/gemini-2.5-pro"
|
|
58
|
+
GEMINI_2_5_FLASH = "models/gemini-2.5-flash"
|
|
59
59
|
GEMINI_2_5_FLASH_LITE = "models/gemini-2.5-flash-lite"
|
|
60
|
-
GEMINI_3_PRO = "models/gemini-3-pro-preview"
|
|
61
60
|
GEMINI_3_FLASH = "models/gemini-3-flash-preview"
|
|
61
|
+
GEMINI_3_1_PRO = "models/gemini-3.1-pro-preview"
|
|
62
|
+
GEMINI_3_1_FLASH_LITE = "models/gemini-3.1-flash-lite"
|
|
63
|
+
GEMINI_3_5_FLASH = "models/gemini-3.5-flash"
|
|
62
64
|
|
|
63
65
|
|
|
64
66
|
class AnthropicModelVersion(str, Enum):
|
|
65
|
-
CLAUDE_SONNET_4 = "claude-sonnet-4-0"
|
|
66
67
|
CLAUDE_OPUS_4 = "claude-opus-4-0"
|
|
67
|
-
CLAUDE_OPUS_4_5 = "claude-opus-4-5"
|
|
68
68
|
CLAUDE_OPUS_4_1 = "claude-opus-4-1"
|
|
69
|
+
CLAUDE_OPUS_4_5 = "claude-opus-4-5"
|
|
70
|
+
CLAUDE_OPUS_4_6 = "claude-opus-4-6"
|
|
71
|
+
CLAUDE_SONNET_4 = "claude-sonnet-4-0"
|
|
69
72
|
CLAUDE_SONNET_4_5 = "claude-sonnet-4-5"
|
|
73
|
+
CLAUDE_SONNET_4_6 = "claude-sonnet-4-6"
|
|
70
74
|
CLAUDE_HAIKU_4_5 = "claude-haiku-4-5"
|
|
71
75
|
|
|
72
76
|
|