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.
Files changed (88) hide show
  1. {superwise_api-4.17.0 → superwise_api-4.18.0}/PKG-INFO +2 -1
  2. {superwise_api-4.17.0 → superwise_api-4.18.0}/pyproject.toml +2 -1
  3. superwise_api-4.18.0/superwise_api/async_client/__init__.py +3 -0
  4. superwise_api-4.18.0/superwise_api/async_client/client.py +74 -0
  5. superwise_api-4.18.0/superwise_api/async_client/entities/__init__.py +4 -0
  6. superwise_api-4.18.0/superwise_api/async_client/entities/dataset.py +29 -0
  7. superwise_api-4.18.0/superwise_api/async_client/entities/sentinel.py +83 -0
  8. superwise_api-4.18.0/superwise_api/async_client/transport.py +76 -0
  9. superwise_api-4.18.0/superwise_api/auth.py +40 -0
  10. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/dataset.py +18 -42
  11. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/destination.py +12 -3
  12. superwise_api-4.18.0/superwise_api/entities/sentinel.py +141 -0
  13. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/agent/agent.py +16 -12
  14. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dataset/dataset_schema.py +11 -0
  15. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/destination/destination.py +35 -11
  16. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/guardrails/guardrails.py +16 -0
  17. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/integration/integration.py +1 -0
  18. superwise_api-4.18.0/superwise_api/models/sentinel/sentinel.py +26 -0
  19. superwise_api-4.18.0/superwise_api/models/tool/__init__.py +0 -0
  20. superwise_api-4.18.0/superwise_api/request_builders/__init__.py +0 -0
  21. superwise_api-4.18.0/superwise_api/request_builders/dataset_request_builder.py +63 -0
  22. superwise_api-4.18.0/superwise_api/request_builders/requests.py +87 -0
  23. superwise_api-4.18.0/superwise_api/request_builders/sentinel_request_builder.py +124 -0
  24. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/superwise_client.py +8 -27
  25. {superwise_api-4.17.0 → superwise_api-4.18.0}/README.md +0 -0
  26. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/__init__.py +0 -0
  27. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/client/__init__.py +0 -0
  28. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/client/api_client.py +0 -0
  29. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/client/api_response.py +0 -0
  30. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/client/configuration.py +0 -0
  31. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/client/exceptions.py +0 -0
  32. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/client/models/page.py +0 -0
  33. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/client/models/threshold_settings.py +0 -0
  34. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/client/rest.py +0 -0
  35. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/config.py +0 -0
  36. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/__init__.py +0 -0
  37. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/agent.py +0 -0
  38. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/base.py +0 -0
  39. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/dashboard.py +0 -0
  40. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/dashboard_item.py +0 -0
  41. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/dataset_source.py +0 -0
  42. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/guardrails.py +0 -0
  43. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/integration.py +0 -0
  44. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/knowledge.py +0 -0
  45. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/mcp.py +0 -0
  46. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/model.py +0 -0
  47. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/model_provider.py +0 -0
  48. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/policy.py +0 -0
  49. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/source.py +0 -0
  50. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/entities/tag.py +0 -0
  51. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/errors.py +0 -0
  52. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/__init__.py +0 -0
  53. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/agent/__init__.py +0 -0
  54. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/agent/feedback.py +0 -0
  55. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/agent/flowise.py +0 -0
  56. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/agent/playground.py +0 -0
  57. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/context/__init__.py +0 -0
  58. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/context/context.py +0 -0
  59. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dashboard/__init__.py +0 -0
  60. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dashboard/dashboard.py +0 -0
  61. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dashboard/query.py +0 -0
  62. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dashboard_item/__init__.py +0 -0
  63. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dashboard_item/dashboard_item.py +0 -0
  64. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dataset/__init__.py +0 -0
  65. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dataset/dataset.py +0 -0
  66. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dataset_source/__init__.py +0 -0
  67. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/dataset_source/dataset_source.py +0 -0
  68. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/destination/__init__.py +0 -0
  69. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/guardrails/__init__.py +0 -0
  70. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/integration/__init__.py +0 -0
  71. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/knowledge/__init__.py +0 -0
  72. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/knowledge/knowledge.py +0 -0
  73. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/mcp/__init__.py +0 -0
  74. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/mcp/mcp.py +0 -0
  75. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/model/__init__.py +0 -0
  76. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/model/model.py +0 -0
  77. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/model_provider/__init__.py +0 -0
  78. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/model_provider/model_provider.py +0 -0
  79. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/policy/__init__.py +0 -0
  80. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/policy/policy.py +0 -0
  81. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/policy/query.py +0 -0
  82. {superwise_api-4.17.0/superwise_api/models/source → superwise_api-4.18.0/superwise_api/models/sentinel}/__init__.py +0 -0
  83. {superwise_api-4.17.0/superwise_api/models/tag → superwise_api-4.18.0/superwise_api/models/source}/__init__.py +0 -0
  84. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/source/source.py +0 -0
  85. {superwise_api-4.17.0/superwise_api/models/tool → superwise_api-4.18.0/superwise_api/models/tag}/__init__.py +0 -0
  86. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/tag/tag.py +0 -0
  87. {superwise_api-4.17.0 → superwise_api-4.18.0}/superwise_api/models/tool/tool.py +0 -0
  88. {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.17.0
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.17.0"
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,3 @@
1
+ from superwise_api.async_client.client import AsyncSuperwiseClient
2
+
3
+ __all__ = ["AsyncSuperwiseClient"]
@@ -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,4 @@
1
+ from superwise_api.async_client.entities.dataset import AsyncDatasetAPI
2
+ from superwise_api.async_client.entities.sentinel import AsyncSentinelAPI
3
+
4
+ __all__ = ["AsyncDatasetAPI", "AsyncSentinelAPI"]
@@ -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 new schema of the dataset.
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
- """Log a record to a dataset.
234
-
235
- Args:
236
- dataset_id: The dataset id.
237
- data: Record data as a dict.
238
- images: Optional image uploads keyed by schema field name.
239
- Each value is a ``(image_bytes, content_type)`` tuple,
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
- f"{self._resource_path}/{dataset_id}/log",
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
- """Log multiple records via ``POST /v1/datasets/log-batch`` (JSON body, ``x-dataset-id`` header).
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
- f"{self._resource_path}/log-batch",
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 = {"dataset_id": dataset_id, "image_id": image_id, "thumbnail": thumbnail}
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: TeamsDestinationTestRequest | SlackDestinationTestRequest | EmailDestinationTestRequest,
134
+ request: (
135
+ TeamsDestinationTestRequest
136
+ | SlackDestinationTestRequest
137
+ | EmailDestinationTestRequest
138
+ | WebhookDestinationTestRequest
139
+ ),
133
140
  **kwargs,
134
141
  ) -> None:
135
142
  """
136
- Test a webhook connection by sending a test notification.
143
+ Test a destination by sending a test notification.
137
144
 
138
145
  Args:
139
- request (TeamsDestinationTestRequest | SlackDestinationTestRequest | EmailDestinationTestRequest): The test connection request payload.
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
- GPT_5_2 = "gpt-5.2"
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