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.
Files changed (90) hide show
  1. {superwise_api-4.16.0 → superwise_api-4.18.0}/PKG-INFO +3 -1
  2. {superwise_api-4.16.0 → superwise_api-4.18.0}/pyproject.toml +3 -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.16.0 → superwise_api-4.18.0}/superwise_api/client/api_client.py +25 -11
  11. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/client/rest.py +26 -12
  12. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/agent.py +139 -17
  13. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/dataset.py +73 -43
  14. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/destination.py +56 -7
  15. superwise_api-4.18.0/superwise_api/entities/sentinel.py +141 -0
  16. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/agent/agent.py +50 -12
  17. superwise_api-4.18.0/superwise_api/models/agent/feedback.py +16 -0
  18. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/agent/playground.py +3 -0
  19. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dataset/dataset.py +4 -0
  20. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dataset/dataset_schema.py +11 -0
  21. superwise_api-4.18.0/superwise_api/models/destination/destination.py +106 -0
  22. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/guardrails/guardrails.py +16 -0
  23. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/integration/integration.py +3 -0
  24. superwise_api-4.18.0/superwise_api/models/sentinel/sentinel.py +26 -0
  25. superwise_api-4.18.0/superwise_api/models/tool/__init__.py +0 -0
  26. superwise_api-4.18.0/superwise_api/request_builders/__init__.py +0 -0
  27. superwise_api-4.18.0/superwise_api/request_builders/dataset_request_builder.py +63 -0
  28. superwise_api-4.18.0/superwise_api/request_builders/requests.py +87 -0
  29. superwise_api-4.18.0/superwise_api/request_builders/sentinel_request_builder.py +124 -0
  30. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/superwise_client.py +8 -27
  31. superwise_api-4.16.0/superwise_api/models/agent/feedback.py +0 -41
  32. superwise_api-4.16.0/superwise_api/models/destination/destination.py +0 -37
  33. {superwise_api-4.16.0 → superwise_api-4.18.0}/README.md +0 -0
  34. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/__init__.py +0 -0
  35. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/client/__init__.py +0 -0
  36. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/client/api_response.py +0 -0
  37. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/client/configuration.py +0 -0
  38. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/client/exceptions.py +0 -0
  39. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/client/models/page.py +0 -0
  40. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/client/models/threshold_settings.py +0 -0
  41. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/config.py +0 -0
  42. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/__init__.py +0 -0
  43. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/base.py +0 -0
  44. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/dashboard.py +0 -0
  45. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/dashboard_item.py +0 -0
  46. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/dataset_source.py +0 -0
  47. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/guardrails.py +0 -0
  48. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/integration.py +0 -0
  49. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/knowledge.py +0 -0
  50. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/mcp.py +0 -0
  51. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/model.py +0 -0
  52. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/model_provider.py +0 -0
  53. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/policy.py +0 -0
  54. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/source.py +0 -0
  55. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/entities/tag.py +0 -0
  56. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/errors.py +0 -0
  57. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/__init__.py +0 -0
  58. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/agent/__init__.py +0 -0
  59. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/agent/flowise.py +0 -0
  60. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/context/__init__.py +0 -0
  61. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/context/context.py +0 -0
  62. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dashboard/__init__.py +0 -0
  63. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dashboard/dashboard.py +0 -0
  64. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dashboard/query.py +0 -0
  65. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dashboard_item/__init__.py +0 -0
  66. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dashboard_item/dashboard_item.py +0 -0
  67. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dataset/__init__.py +0 -0
  68. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dataset_source/__init__.py +0 -0
  69. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/dataset_source/dataset_source.py +0 -0
  70. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/destination/__init__.py +0 -0
  71. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/guardrails/__init__.py +0 -0
  72. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/integration/__init__.py +0 -0
  73. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/knowledge/__init__.py +0 -0
  74. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/knowledge/knowledge.py +0 -0
  75. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/mcp/__init__.py +0 -0
  76. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/mcp/mcp.py +0 -0
  77. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/model/__init__.py +0 -0
  78. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/model/model.py +0 -0
  79. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/model_provider/__init__.py +0 -0
  80. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/model_provider/model_provider.py +0 -0
  81. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/policy/__init__.py +0 -0
  82. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/policy/policy.py +0 -0
  83. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/policy/query.py +0 -0
  84. {superwise_api-4.16.0/superwise_api/models/source → superwise_api-4.18.0/superwise_api/models/sentinel}/__init__.py +0 -0
  85. {superwise_api-4.16.0/superwise_api/models/tag → superwise_api-4.18.0/superwise_api/models/source}/__init__.py +0 -0
  86. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/source/source.py +0 -0
  87. {superwise_api-4.16.0/superwise_api/models/tool → superwise_api-4.18.0/superwise_api/models/tag}/__init__.py +0 -0
  88. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/tag/tag.py +0 -0
  89. {superwise_api-4.16.0 → superwise_api-4.18.0}/superwise_api/models/tool/tool.py +0 -0
  90. {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.16.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,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.16.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"
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,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())
@@ -7,7 +7,7 @@ import re
7
7
  import tempfile
8
8
  import uuid
9
9
  from multiprocessing.pool import ThreadPool
10
- from typing import Optional
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: Optional[dict] = None, **kwargs) -> BaseModel:
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: Optional[dict] = None, query_params: dict | None = None, **kwargs
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 = ["files", "async_req", "_request_auth", "_content_type", "_headers", "response_types_map"]
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
- _content_types_list = _params.get("_content_type", self.select_header_content_type(["application/json"]))
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: Optional[dict] = None, **kwargs
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: Optional[dict] = None,
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
- request_body = None
428
- if body is not None:
429
- request_body = json.dumps(body)
430
-
431
- r = self.pool_manager.request(
432
- "POST",
433
- url,
434
- body=request_body,
435
- preload_content=False,
436
- timeout=timeout,
437
- headers=headers,
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)