borgee-plugin-sdk 0.1.2__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.
@@ -0,0 +1,125 @@
1
+ """Deterministic in-memory transport for SDK consumers' tests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from collections import deque
7
+ from collections.abc import AsyncIterator
8
+ from dataclasses import dataclass
9
+ from typing import Any
10
+
11
+ from ..errors import BorgeeError, NotOnlineError
12
+ from ..models import ConnectionState, ConnectionStatus, DeliveryEvent
13
+
14
+
15
+ @dataclass(frozen=True, slots=True)
16
+ class RecordedAction:
17
+ operation: str
18
+ payload: dict[str, Any]
19
+
20
+
21
+ class FakeDelivery:
22
+ def __init__(self, event: DeliveryEvent) -> None:
23
+ self.event = event
24
+ self.cursor = event.cursor
25
+ self.checkpointed = False
26
+ self.failed = False
27
+
28
+ async def checkpoint(self) -> None:
29
+ if self.failed:
30
+ raise BorgeeError("fake delivery was already failed", "bpp.delivery_resolved")
31
+ self.checkpointed = True
32
+
33
+ async def fail(self, error: BaseException | None = None) -> None:
34
+ if self.checkpointed:
35
+ raise BorgeeError("fake delivery was already checkpointed", "bpp.delivery_resolved")
36
+ self.failed = True
37
+
38
+
39
+ class FakeTransport:
40
+ def __init__(self, *, agent_id: str = "agent-test") -> None:
41
+ self._agent_id = agent_id
42
+ self._state = ConnectionState(ConnectionStatus.CONNECTING)
43
+ self._connected = False
44
+ self.actions: list[RecordedAction] = []
45
+ self.task_frames: list[dict[str, Any]] = []
46
+ self._results: deque[Any] = deque()
47
+ self._delivery_queue: asyncio.Queue[FakeDelivery | None] = asyncio.Queue()
48
+ self._state_queue: asyncio.Queue[ConnectionState] = asyncio.Queue()
49
+
50
+ @property
51
+ def agent_id(self) -> str:
52
+ return self._agent_id
53
+
54
+ @property
55
+ def state(self) -> ConnectionState:
56
+ return self._state
57
+
58
+ def queue_result(self, value: Any) -> None:
59
+ self._results.append(value)
60
+
61
+ def queue_error(self, error: BaseException) -> None:
62
+ self._results.append(error)
63
+
64
+ async def emit(self, event: DeliveryEvent) -> FakeDelivery:
65
+ delivery = FakeDelivery(event)
66
+ await self._delivery_queue.put(delivery)
67
+ return delivery
68
+
69
+ async def connect(self) -> None:
70
+ self._connected = True
71
+ self._set_state(ConnectionState(ConnectionStatus.ONLINE))
72
+
73
+ async def close(self) -> None:
74
+ self._connected = False
75
+ self._set_state(ConnectionState(ConnectionStatus.CLOSED))
76
+ await self._delivery_queue.put(None)
77
+
78
+ async def perform(self, operation: str, payload: dict[str, Any]) -> Any:
79
+ if not self._connected:
80
+ raise NotOnlineError()
81
+ self.actions.append(RecordedAction(operation, payload))
82
+ if not self._results:
83
+ return None
84
+ result = self._results.popleft()
85
+ if isinstance(result, BaseException):
86
+ raise result
87
+ return result
88
+
89
+ async def report_task_started(self, task_id: str, channel_id: str, subject: str) -> None:
90
+ if not self._connected:
91
+ raise NotOnlineError()
92
+ self.task_frames.append(
93
+ {"type": "task_started", "task_id": task_id, "channel_id": channel_id, "subject": subject}
94
+ )
95
+
96
+ async def report_task_finished(self, task_id: str, channel_id: str, outcome: str, reason: str) -> None:
97
+ if not self._connected:
98
+ raise NotOnlineError()
99
+ self.task_frames.append(
100
+ {
101
+ "type": "task_finished",
102
+ "task_id": task_id,
103
+ "channel_id": channel_id,
104
+ "outcome": outcome,
105
+ "reason": reason,
106
+ }
107
+ )
108
+
109
+ async def deliveries(self) -> AsyncIterator[FakeDelivery]:
110
+ while True:
111
+ delivery = await self._delivery_queue.get()
112
+ if delivery is None:
113
+ return
114
+ yield delivery
115
+
116
+ async def state_changes(self) -> AsyncIterator[ConnectionState]:
117
+ while True:
118
+ state = await self._state_queue.get()
119
+ yield state
120
+ if state.status is ConnectionStatus.CLOSED:
121
+ return
122
+
123
+ def _set_state(self, state: ConnectionState) -> None:
124
+ self._state = state
125
+ self._state_queue.put_nowait(state)
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: borgee-plugin-sdk
3
+ Version: 0.1.2
4
+ Summary: Async Python SDK for Borgee plugin runtimes
5
+ License-Expression: MIT
6
+ Classifier: Development Status :: 3 - Alpha
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Typing :: Typed
12
+ Requires-Python: <3.14,>=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: websockets==15.0.1
16
+ Provides-Extra: test
17
+ Requires-Dist: build==1.2.2.post1; extra == "test"
18
+ Requires-Dist: mypy==1.17.0; extra == "test"
19
+ Requires-Dist: PyYAML==6.0.2; extra == "test"
20
+ Requires-Dist: pytest==9.0.3; extra == "test"
21
+ Requires-Dist: pytest-asyncio==1.3.0; extra == "test"
22
+ Requires-Dist: pytest-timeout==2.4.0; extra == "test"
23
+ Requires-Dist: ruff==0.12.4; extra == "test"
24
+ Requires-Dist: twine==6.1.0; extra == "test"
25
+ Dynamic: license-file
26
+
27
+ # Borgee Plugin SDK for Python
28
+
29
+ `borgee-plugin-sdk` connects a Python runtime to Borgee over the Borgee Plugin
30
+ Protocol (BPP). It supports Python 3.11 through 3.13 and pins
31
+ `websockets==15.0.1` because the connection hardening depends on that exact
32
+ client implementation.
33
+
34
+ Build a wheel from this directory or install the source checkout for evaluation. The repository also provides a manual workflow for publishing to TestPyPI and PyPI.
35
+
36
+ ```bash
37
+ python -m pip install .
38
+ ```
39
+
40
+ ```python
41
+ import asyncio
42
+
43
+ from borgee_plugin_sdk import BorgeePluginOptions, ReplayMode, create_borgee_plugin
44
+
45
+
46
+ async def main() -> None:
47
+ client = create_borgee_plugin(
48
+ BorgeePluginOptions(
49
+ base_url="https://chat.example.com",
50
+ api_key="bgr_example",
51
+ replay_mode=ReplayMode.LATEST_N,
52
+ )
53
+ )
54
+ async with client:
55
+ async for delivery in client.deliveries():
56
+ print(delivery.event)
57
+ await delivery.checkpoint()
58
+
59
+
60
+ asyncio.run(main())
61
+ ```
62
+
63
+ Every inbound message or replay summary is a `Delivery`. The iterator has one
64
+ consumer, and it will not advance until the consumer calls `checkpoint()` or
65
+ `fail()`. Actions fail while the connection is resuming; replay consumption
66
+ continues until the client becomes online.
67
+
68
+ For local development, insecure transport is restricted to loopback origins and
69
+ requires `allow_insecure_loopback=True`. API keys are sent only in the WebSocket
70
+ `Authorization` header. Redirects are rejected.
71
+
72
+ This package is licensed under the MIT License.
@@ -0,0 +1,16 @@
1
+ borgee_plugin_sdk/__init__.py,sha256=mkHNldPhkI_cqjcewoIDNBcsJEVaXagz4bC0KKLsQuQ,1738
2
+ borgee_plugin_sdk/_connection.py,sha256=9e5sWDqBKjxA_ep7r3BZi5kHNNhwZvdgAQNIRUn2KqA,71893
3
+ borgee_plugin_sdk/client.py,sha256=86i6gnT2VpngHWG_pyqSj-L0O5oOeVAiAYTArFPj0kA,14379
4
+ borgee_plugin_sdk/cursor.py,sha256=pupAO9Ibz1HW8j3z8Pfby9IYqxEjgyHl_ORh1LP74lc,8315
5
+ borgee_plugin_sdk/errors.py,sha256=fwdyeQ74DPTborJ6Xsrw6lEGuAvQl_0TEfA7_QVCN0s,1926
6
+ borgee_plugin_sdk/models.py,sha256=9re3KR5GYOjuRXTtSrjgRNTDdr5TeEmBqD316pXGNTE,9073
7
+ borgee_plugin_sdk/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
8
+ borgee_plugin_sdk/_wire/__init__.py,sha256=0a0KwUmK_dVKAavxkjLc7X8vPzjEve3VfBWLdWdOCg4,163
9
+ borgee_plugin_sdk/_wire/frames_generated.py,sha256=RTjg0uvhoeyczpLqzg1JPRToGf-uYxmQyMlmUeTz8PM,6475
10
+ borgee_plugin_sdk/testing/__init__.py,sha256=qr6O4dxujS9VC5AGxlYwhGnhb_jSWGGPHC5AbWR1MrY,534
11
+ borgee_plugin_sdk/testing/fake_transport.py,sha256=xwPsmgGqkURmeVJaJdIpPzly2BRVtiZgaZJbklIZ4fk,4178
12
+ borgee_plugin_sdk-0.1.2.dist-info/licenses/LICENSE,sha256=Zw1UDBR9zRXe3VzNzoW0GSdU-AgMJMPdDkTFHXL_Weg,1076
13
+ borgee_plugin_sdk-0.1.2.dist-info/METADATA,sha256=bqU4KmVrA3aS7UeNrodT7DzqNmfgO8_wx43YKKA20CQ,2498
14
+ borgee_plugin_sdk-0.1.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
15
+ borgee_plugin_sdk-0.1.2.dist-info/top_level.txt,sha256=cfqq-Jl2KXSJ2YeP0E1QlcdewQHrEc9dDTx0CZ4s-dc,18
16
+ borgee_plugin_sdk-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Borgee contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ borgee_plugin_sdk