visionai-sdk-python 2.2.0.dev4__py3-none-any.whl → 2.2.0.dev5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -58,7 +58,11 @@ class AsyncClient(_BaseClient):
58
58
  async def request(self, method: str, url: str, **kwargs) -> httpx.Response:
59
59
  """Send any request with this client's bearer token.
60
60
 
61
- ``url`` is a full URL (the client has two bases); ``kwargs`` go to httpx.
61
+ ``url`` is a full URL (the client has two bases); ``kwargs`` go to httpx. The
62
+ bearer token goes to whatever host ``url`` names, so ``url`` must come from
63
+ configuration or code, never from a caller's input; redirects are not
64
+ followed unless asked, and httpx drops the header on a cross-origin redirect.
65
+
62
66
  With a ``credential``, a 401 buys one retry with the credential's current
63
67
  token, which it replaces when the rejected one is old enough to have been
64
68
  revoked (``REPLACE_COOLDOWN_S``); a 401 on the retry raises. Without a
@@ -237,7 +237,8 @@ class AsyncClientCredentials(_Credentials):
237
237
 
238
238
  Pass it to ``AsyncClient(credential=...)`` and use ``await get_token()`` for
239
239
  requests the SDK does not make itself, such as a WebSocket upgrade. One
240
- instance serves any number of clients and callers.
240
+ instance serves any number of clients and callers on one event loop; its
241
+ lock and ``httpx.AsyncClient`` bind to that loop.
241
242
  """
242
243
 
243
244
  def __init__(
@@ -58,7 +58,11 @@ class Client(_BaseClient):
58
58
  def request(self, method: str, url: str, **kwargs) -> httpx.Response:
59
59
  """Send any request with this client's bearer token.
60
60
 
61
- ``url`` is a full URL (the client has two bases); ``kwargs`` go to httpx.
61
+ ``url`` is a full URL (the client has two bases); ``kwargs`` go to httpx. The
62
+ bearer token goes to whatever host ``url`` names, so ``url`` must come from
63
+ configuration or code, never from a caller's input; redirects are not
64
+ followed unless asked, and httpx drops the header on a cross-origin redirect.
65
+
62
66
  With a ``credential``, a 401 buys one retry with the credential's current
63
67
  token, which it replaces when the rejected one is old enough to have been
64
68
  revoked (``REPLACE_COOLDOWN_S``); a 401 on the retry raises. Without a
@@ -9,6 +9,7 @@ from .protocol import (
9
9
  HEARTBEAT_INTERVAL_S,
10
10
  MAX_BODY_BYTES,
11
11
  MAX_FRAME_BYTES,
12
+ MAX_MESSAGE_CHARS,
12
13
  REGISTER_DEADLINE_S,
13
14
  RELAY_TIMEOUT_S,
14
15
  STALE_AFTER_S,
@@ -18,6 +19,7 @@ from .protocol import (
18
19
  Error,
19
20
  ErrorCode,
20
21
  Frame,
22
+ FrameTooLarge,
21
23
  Heartbeat,
22
24
  InvalidFrame,
23
25
  ModelEntry,
@@ -40,6 +42,7 @@ __all__ = [
40
42
  "HEARTBEAT_INTERVAL_S",
41
43
  "MAX_BODY_BYTES",
42
44
  "MAX_FRAME_BYTES",
45
+ "MAX_MESSAGE_CHARS",
43
46
  "REGISTER_DEADLINE_S",
44
47
  "RELAY_TIMEOUT_S",
45
48
  "STALE_AFTER_S",
@@ -49,6 +52,7 @@ __all__ = [
49
52
  "Error",
50
53
  "ErrorCode",
51
54
  "Frame",
55
+ "FrameTooLarge",
52
56
  "Heartbeat",
53
57
  "InvalidFrame",
54
58
  "ModelEntry",
@@ -28,6 +28,8 @@ MAX_BODY_BYTES = 15 * 1024 * 1024
28
28
  # Timeouts nest: the agent waits for VS, the relay waits a little longer for the agent.
29
29
  AGENT_TIMEOUT_S = 100
30
30
  RELAY_TIMEOUT_S = 110.0
31
+ # An `error` echoes parts of the peer's frame; cut them so the error fits a frame.
32
+ MAX_MESSAGE_CHARS = 200
31
33
 
32
34
  # Platform workspace ids: lowercase hyphenated UUIDs.
33
35
  UUID_RE = re.compile(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")
@@ -58,6 +60,7 @@ class ErrorCode(StrEnum):
58
60
  PATH_NOT_ALLOWED = "path_not_allowed"
59
61
  UPSTREAM_UNREACHABLE = "upstream_unreachable"
60
62
  UPSTREAM_TIMEOUT = "upstream_timeout"
63
+ UPSTREAM_TOO_LARGE = "upstream_too_large"
61
64
 
62
65
 
63
66
  class Frame(BaseModel):
@@ -133,13 +136,30 @@ _relay_frame = TypeAdapter(Annotated[RelayFrame, Field(discriminator="type")])
133
136
 
134
137
 
135
138
  class InvalidFrame(ValueError):
136
- """A frame the receiver cannot read; the text is safe to send back in an ``error``."""
139
+ """A frame the receiver cannot read; the text is safe to send back in an ``error``.
140
+
141
+ Parts of the text come from the peer's frame, so it is cut at ``MAX_MESSAGE_CHARS``
142
+ and a ``request_id`` longer than that is dropped.
143
+ """
137
144
 
138
145
  def __init__(self, message: str, request_id: str | None = None) -> None:
146
+ if len(message) > MAX_MESSAGE_CHARS:
147
+ message = message[: MAX_MESSAGE_CHARS - 1] + "…"
148
+ if request_id is not None and len(request_id) > MAX_MESSAGE_CHARS:
149
+ request_id = None
150
+ message += " (request_id too long, dropped)"
139
151
  super().__init__(message)
140
152
  self.request_id = request_id
141
153
 
142
154
 
155
+ class FrameTooLarge(ValueError):
156
+ """The encoded frame exceeds ``MAX_FRAME_BYTES``; the receiver would close with 1009."""
157
+
158
+ def __init__(self, size: int) -> None:
159
+ super().__init__(f"frame is {size} bytes; the limit is {MAX_FRAME_BYTES}")
160
+ self.size = size
161
+
162
+
143
163
  def parse_agent_frame(text: str) -> AgentFrame:
144
164
  """A frame the relay received from an agent."""
145
165
  return _parse(text, _agent_frame)
@@ -175,4 +195,9 @@ def _parse(text: str, adapter: TypeAdapter):
175
195
 
176
196
 
177
197
  def dump_frame(frame: BaseModel) -> str:
178
- return frame.model_dump_json(exclude_none=True)
198
+ """The frame as JSON text, or ``FrameTooLarge`` when a receiver would refuse it."""
199
+ text = frame.model_dump_json(exclude_none=True)
200
+ size = len(text.encode())
201
+ if size > MAX_FRAME_BYTES:
202
+ raise FrameTooLarge(size)
203
+ return text
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: visionai-sdk-python
3
- Version: 2.2.0.dev4
3
+ Version: 2.2.0.dev5
4
4
  Summary: VisionAI SDK for Python
5
5
  Author-email: Tony Yang <tonyyang@linkervision.com>
6
6
  Requires-Python: >=3.11
@@ -148,7 +148,8 @@ client = Client(auth_url="...", vlm_url="...", credential=cred) # vlm.chat() u
148
148
 
149
149
  token = cred.get_token().token # for a WebSocket upgrade or another library
150
150
 
151
- # Any other HTTP call with the same token; a 401 gets one fresh token and one retry
151
+ # Any other HTTP call with the same token, sent to the host you name; a 401 gets one
152
+ # fresh token and one retry
152
153
  nodes = client.request("GET", "https://vs.example.com/mgmt/nodes").json()
153
154
  ```
154
155
 
@@ -168,7 +169,7 @@ authentication wins. `vlm_url` is optional for clients that only authenticate.
168
169
 
169
170
  `visionai_sdk_python.relay` holds the wire format between platform-relay and
170
171
  platform-edge-agent: the frame models, the constants and `parse_agent_frame()` /
171
- `parse_relay_frame()` / `dump_frame()`. The spec is platform-service's
172
+ `parse_relay_frame()` / `dump_frame()` (which refuses a frame over `MAX_FRAME_BYTES`). The spec is platform-service's
172
173
  `docs/adr/0001-relay-protocol.md`; both sides import this module instead of keeping a
173
174
  copy. The ADR's JSON examples ship with it (`visionai_sdk_python.relay.examples`:
174
175
  `example(name)` / `example_text(name)`), so every implementation tests against the same
@@ -3,8 +3,8 @@ visionai_sdk_python/_base.py,sha256=q8Ybwk9uEdaAFh85IRCHvsSpXEvYFosFYGhJAeQNIr4,
3
3
  visionai_sdk_python/_jwt_verifier.py,sha256=h4CAK1iiDAJ-oKnXrLbCJw5rWcsVe94QNY12ne1Wzto,8442
4
4
  visionai_sdk_python/_request_attribution.py,sha256=zQk_bSLsu1RTp1Fe3MGUUX8YRzq3iqfBTmhhjM7BuRU,1907
5
5
  visionai_sdk_python/_source_header.py,sha256=DWiR-TGclET4UrqmAEO7bYPtf9XuLSE4FGQjZIgVWiY,5133
6
- visionai_sdk_python/async_client.py,sha256=JHx7Kz3l84PdyHvgGPhpTKprRdSabXxjqazqUVj--rY,5542
7
- visionai_sdk_python/client.py,sha256=0B3ut9iXsiZxveOIP8SEbTZwv3D0mDZ62D0ycIFOBC8,5352
6
+ visionai_sdk_python/async_client.py,sha256=T_VyweNx6_u5BgQ21B5EMoj1U2LZh64wlDUtoVqYOow,5795
7
+ visionai_sdk_python/client.py,sha256=3XzxP5Db-krmDSFbIJ7UnlZo11pIbmuDzlJkZiLX9Ys,5605
8
8
  visionai_sdk_python/constants.py,sha256=KWhoxZdlwqXwhUJDqERStq2mrms-MA8aRDqBwbIvNO0,1471
9
9
  visionai_sdk_python/endpoints.py,sha256=CC-AoxXzg39sMAhqx1CTS3gRWKx_H7VOqBANRLVotoU,183
10
10
  visionai_sdk_python/exceptions.py,sha256=BUpcoTgILc3z68lIQGyTYs7PCvu_BTSi228YMTx7ZWs,3020
@@ -13,11 +13,11 @@ visionai_sdk_python/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
13
13
  visionai_sdk_python/auth/__init__.py,sha256=sWtanqUos5Q8P80IvjoyvOzXv6gYNkflop9qwDwsZk8,400
14
14
  visionai_sdk_python/auth/_mixin.py,sha256=EDDhEB9jHc_7wEoLx_MprhToTVLekL5CyJuLTK8GhNM,5022
15
15
  visionai_sdk_python/auth/async_resource.py,sha256=V6O51i7KyYSBkiQeKAbmw-EIKMVZIozO1gDexTX6tXw,5535
16
- visionai_sdk_python/auth/credentials.py,sha256=nsZkavY8pDT39gZxFaKUcl-X_JfKKkc3Nkv9fSFFP4Y,10892
16
+ visionai_sdk_python/auth/credentials.py,sha256=SZFu1LAemkjdd7nfrHqtxK72w_TtBrCF03o0ys6Z1Xo,10968
17
17
  visionai_sdk_python/auth/models.py,sha256=u6MDFYaWMyf08XCrGZzwPHv43d6AElH9XO2QSjRsb3Q,449
18
18
  visionai_sdk_python/auth/resource.py,sha256=3PgcxH8N6unKTs2jSQhMeJT_lg6bTce0hz0bzuID12U,5446
19
- visionai_sdk_python/relay/__init__.py,sha256=N4DuhutN8P4EWZRFIaWTqvylNsLkzRSkHMRRF2pknFk,1296
20
- visionai_sdk_python/relay/protocol.py,sha256=Hy7J4kgDxaKy0WOrtkmqD5xUFXgKAVXwTWX4NQ9yQHE,5561
19
+ visionai_sdk_python/relay/__init__.py,sha256=yqYdedHUBCo-E9wmVY9Xfa5r9zbOLeii-p6HYTdgzvY,1384
20
+ visionai_sdk_python/relay/protocol.py,sha256=aIEr4fy7ZRx2g3aBki5mMGbjjnc7AhIAECqAPRARVSY,6611
21
21
  visionai_sdk_python/relay/examples/__init__.py,sha256=GvjlP-EtrhDVgqReOYqcf1mObND61vO6Q8CT_YFfgDM,732
22
22
  visionai_sdk_python/relay/examples/error_connection.json,sha256=stjkXo27Vkhoy0rNtPUti2OEXzioxJ8Aqgyq2hAdquA,81
23
23
  visionai_sdk_python/relay/examples/error_request.json,sha256=iUB11sSf1vuBAdzvoiHAoUoifZoG4-wGBK6gb0ConjA,228
@@ -33,7 +33,7 @@ visionai_sdk_python/vlm/async_resource.py,sha256=meMUfBDa0g1hk7vFZq2fDuIQ1-QUpU0
33
33
  visionai_sdk_python/vlm/models.py,sha256=yDFjo4LfAxqXYBnl6ZW7YUJQoKKRkQWtqBJi2ruI6Jw,984
34
34
  visionai_sdk_python/vlm/preprocess.py,sha256=ZpJJa13h8_YKwh_P7Pn8WIXV0x-r2xJi-ETgCFsGw-Y,7892
35
35
  visionai_sdk_python/vlm/resource.py,sha256=4eMQ4ACcgd7nUfdRyADWmPd8yZ--uqQC4zwzBft68lg,3386
36
- visionai_sdk_python-2.2.0.dev4.dist-info/METADATA,sha256=sTQXZIrAJy5ANXFc2Up7r5BIeY8adzLhErAmTJh0Bjw,30521
37
- visionai_sdk_python-2.2.0.dev4.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
38
- visionai_sdk_python-2.2.0.dev4.dist-info/top_level.txt,sha256=79a5MCSLLv6Mbfun-8RS-G6O927hahBwDqJjeuWLdvM,20
39
- visionai_sdk_python-2.2.0.dev4.dist-info/RECORD,,
36
+ visionai_sdk_python-2.2.0.dev5.dist-info/METADATA,sha256=LDZ7P4QHkvXJk6_6nHx9qLUOGYVBuExwt_ZSURtF2lk,30597
37
+ visionai_sdk_python-2.2.0.dev5.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
38
+ visionai_sdk_python-2.2.0.dev5.dist-info/top_level.txt,sha256=79a5MCSLLv6Mbfun-8RS-G6O927hahBwDqJjeuWLdvM,20
39
+ visionai_sdk_python-2.2.0.dev5.dist-info/RECORD,,