vention-communication 0.1.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.
@@ -0,0 +1,302 @@
1
+ Metadata-Version: 2.4
2
+ Name: vention-communication
3
+ Version: 0.1.0
4
+ Summary: A framework for storing and managing component and application data for machine apps.
5
+ License: Proprietary
6
+ Author: VentionCo
7
+ Requires-Python: >=3.10,<3.11
8
+ Classifier: License :: Other/Proprietary License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Requires-Dist: fastapi (>=0.116.1,<0.117.0)
12
+ Requires-Dist: uvicorn (>=0.35.0,<0.36.0)
13
+ Description-Content-Type: text/markdown
14
+
15
+ # Vention Communication
16
+
17
+ A thin, FastAPI-powered RPC layer for machine-apps that exposes Connect-compatible request-response and server-streaming endpoints — plus .proto generation from Python decorators, allowing typed SDKs to be generated separately via Buf.
18
+
19
+ ## Table of Contents
20
+
21
+ - [✨ Features](#-features)
22
+ - [🧠 Concepts & Overview](#-concepts--overview)
23
+ - [⚙️ Installation & Setup](#️-installation--setup)
24
+ - [🚀 Quickstart Tutorial](#-quickstart-tutorial)
25
+ - [🛠 How-to Guides](#-how-to-guides)
26
+ - [📖 API Reference](#-api-reference)
27
+ - [🔍 Troubleshooting & FAQ](#-troubleshooting--faq)
28
+
29
+ ## ✨ Features
30
+
31
+ - **Decorator-based RPCs**: `@action()` for request-response calls, `@stream()` for server streams. Types inferred from Python type hints or Pydantic models.
32
+ - **Connect-compatible endpoints**: Works seamlessly with Connect clients — compatible with `@connectrpc/connect-web` and `connectrpc-python`.
33
+ - **FastAPI-based**: Leverages FastAPI for routing and async concurrency.
34
+ - **Single service surface**: All methods exposed under `/rpc/<package.Service>/<Method>`.
35
+ - **Automatic .proto emission**: Generates a single .proto file describing all registered RPCs and models at runtime, ready for use in Buf-based SDK generation.
36
+
37
+ ## 🧠 Concepts & Overview
38
+
39
+ ### What is an RPC?
40
+
41
+ **RPC (Remote Procedure Call)** is a way to call a function on a remote server as if it were a local function. Instead of manually crafting HTTP requests and parsing responses, you define your functions with decorators, and the library handles the network communication for you. Your client code can call `client.ping({ message: "Hello" })` and get back a response, just like calling a local function.
42
+
43
+ ### What is Connect?
44
+
45
+ **Connect** is a protocol for building APIs that combines the simplicity of REST with the type safety of gRPC. It uses Protocol Buffers (protobuf) for defining your API contract, but sends data as JSON over HTTP, making it easy to use from browsers and simple HTTP clients. Connect provides:
46
+
47
+ - **Type safety**: Your API contract is defined in a `.proto` file, ensuring clients and servers agree on the data structure
48
+ - **Error handling**: Standardized error responses that work across languages
49
+ - **Streaming**: Built-in support for server-side streaming (continuously sending updates to clients)
50
+ - **Developer experience**: Works with standard HTTP tools and browsers
51
+
52
+ ### Core Concepts
53
+
54
+ - **Actions (Request-Response)** — send a request, get a response back. Input and output types are inferred from function annotations. If either is missing, `google.protobuf.Empty` is used.
55
+ - **Streams (Server streaming)** — continuous updates broadcast to all subscribers. Each stream can optionally replay the last value when someone subscribes. Queues default to size-1 to always show the latest value.
56
+ - **Service Surface** — all actions and streams belong to one service, e.g. `vention.app.v1.<YourAppName>Service`, with routes mounted under `/rpc`.
57
+ - **Proto Generation** — `VentionApp.finalize()` writes a .proto to disk, capturing all decorated RPCs, inferred models, and scalar wrappers. SDK generation (via Buf) is handled externally.
58
+
59
+ ### Stream Configuration Options
60
+
61
+ When creating a stream with `@stream()`, you can configure how updates are delivered to subscribers:
62
+
63
+ #### `replay` (default: `True`)
64
+
65
+ Controls whether new subscribers receive the last published value immediately when they subscribe.
66
+
67
+ - **`replay=True`**: New subscribers instantly receive the most recent value (if one exists). Useful for state streams where clients need the current state immediately upon connection.
68
+ - **`replay=False`**: New subscribers only receive values published after they subscribe. Useful for event streams where you only want to see new events.
69
+
70
+
71
+ #### `queue_maxsize` (default: `1`)
72
+
73
+ The maximum number of items that can be queued for each subscriber before the delivery policy kicks in.
74
+
75
+ - **`queue_maxsize=1`**: Only the latest value is kept. Perfect for state streams where you only care about the current state.
76
+ - **`queue_maxsize=N`** (N > 1): Allows buffering up to N items. Useful when subscribers might process items slower than they're published, but you still want to limit memory usage.
77
+
78
+ ```python
79
+ # Only keep latest temperature reading
80
+ @stream(name="Temperature", payload=Temperature, queue_maxsize=1)
81
+
82
+ # Buffer up to 10 sensor readings
83
+ @stream(name="SensorData", payload=SensorReading, queue_maxsize=10)
84
+ ```
85
+
86
+ #### `policy` (default: `"latest"`)
87
+
88
+ Controls what happens when a subscriber's queue is full and a new value is published.
89
+
90
+ - **`policy="latest"`**: Drops the oldest item and adds the new one. Ensures subscribers always have the most recent data. Best for state streams where you only care about the current value.
91
+ - **`policy="fifo"`**: Waits for the subscriber to process items and make space in the queue before adding new items. Ensures no data is lost but may cause backpressure if subscribers are slow. Best for event streams where you need to process every event.
92
+
93
+ ```python
94
+ # Latest-wins: always show current state
95
+ @stream(name="Status", payload=Status, policy="latest", queue_maxsize=1)
96
+
97
+ # FIFO: process every event, even if slow
98
+ @stream(name="Events", payload=Event, policy="fifo", queue_maxsize=100)
99
+ ```
100
+
101
+ **Common Combinations:**
102
+
103
+ - **State monitoring** (default): `replay=True`, `queue_maxsize=1`, `policy="latest"` — subscribers get current state immediately and always see the latest value.
104
+ - **Event streaming**: `replay=False`, `queue_maxsize=100`, `policy="fifo"` — subscribers only see new events and process them in order.
105
+
106
+ ## ⚙️ Installation & Setup
107
+
108
+ **Requirements:**
109
+
110
+ - Python 3.10+
111
+ - FastAPI
112
+ - Uvicorn (for serving)
113
+
114
+ **Install:**
115
+
116
+ ```bash
117
+ pip install vention-communication
118
+ ```
119
+
120
+ **Optional client libraries:**
121
+
122
+ - TypeScript: `@connectrpc/connect-web`
123
+ - Python: `connectrpc` with `httpx.AsyncClient`
124
+
125
+ ## 🚀 Quickstart Tutorial
126
+
127
+ ### 1. Define your RPCs
128
+
129
+ ```python
130
+ # demo/main.py
131
+ from pydantic import BaseModel
132
+
133
+ from vention_communication.app import VentionApp
134
+ from vention_communication.decorators import action, stream
135
+
136
+
137
+ class PingRequest(BaseModel):
138
+ message: str
139
+
140
+
141
+ class PingResponse(BaseModel):
142
+ message: str
143
+
144
+
145
+ class Heartbeat(BaseModel):
146
+ value: str
147
+ timestamp: int
148
+
149
+
150
+ app = VentionApp(name="DemoApp", emit_proto=True, proto_path="proto/app.proto")
151
+
152
+
153
+ @action()
154
+ async def ping(req: PingRequest) -> PingResponse:
155
+ return PingResponse(message=f"Pong: {req.message}")
156
+
157
+
158
+ @stream(name="Heartbeat", payload=Heartbeat)
159
+ async def heartbeat_publisher() -> Heartbeat:
160
+ from time import time
161
+ import random
162
+ return Heartbeat(value=f"{random.random():.2f}", timestamp=int(time()))
163
+
164
+
165
+ app.finalize()
166
+ ```
167
+
168
+ **Run:**
169
+
170
+ ```bash
171
+ uvicorn demo.main:app --reload
172
+ ```
173
+
174
+ Routes are exposed under:
175
+
176
+ - `/rpc/vention.app.v1.DemoAppService/Ping`
177
+ - `/rpc/vention.app.v1.DemoAppService/Heartbeat`
178
+
179
+ ### 2. Generated .proto
180
+
181
+ After startup, `proto/app.proto` is emitted automatically.
182
+
183
+ You can now use Buf or protoc to generate client SDKs:
184
+
185
+ ```bash
186
+ buf generate --template buf.gen.ts.yaml
187
+ buf generate --template buf.gen.python.yaml
188
+ ```
189
+
190
+ SDK generation is external to vention-communication — allowing you to control versions and plugins.
191
+
192
+ ### 3. Example TypeScript Client
193
+
194
+ ```typescript
195
+ import { createClient } from "@connectrpc/connect";
196
+ import { createConnectTransport } from "@connectrpc/connect-web";
197
+ import { DemoAppService } from "./gen/connect/proto/app_connect";
198
+
199
+ const transport = createConnectTransport({
200
+ baseUrl: "http://localhost:8000/rpc",
201
+ useBinaryFormat: false,
202
+ });
203
+
204
+ const client = createClient(DemoAppService, transport);
205
+
206
+ const res = await client.ping({ message: "Hello" });
207
+ console.log(res.message);
208
+
209
+ for await (const hb of client.heartbeat({})) {
210
+ console.log("Heartbeat", hb.value, hb.timestamp);
211
+ }
212
+ ```
213
+
214
+ ## 🛠 How-to Guides
215
+
216
+ ### Add a new request-response endpoint
217
+
218
+ ```python
219
+ @action()
220
+ async def get_status() -> dict:
221
+ return {"ok": True}
222
+ ```
223
+
224
+ ### Add a new stream
225
+
226
+ ```python
227
+ @stream(name="Status", payload=dict)
228
+ async def publish_status() -> dict:
229
+ return {"ok": True}
230
+ ```
231
+
232
+ ### Emit proto to a custom path
233
+
234
+ ```python
235
+ app = VentionApp(name="MyService", emit_proto=True, proto_path="out/myservice.proto")
236
+ app.finalize()
237
+ ```
238
+
239
+ ## 📖 API Reference
240
+
241
+ ### VentionApp
242
+
243
+ ```python
244
+ VentionApp(
245
+ name: str = "VentionApp",
246
+ *,
247
+ emit_proto: bool = False,
248
+ proto_path: str = "proto/app.proto",
249
+ **fastapi_kwargs
250
+ )
251
+ ```
252
+
253
+ **Methods:**
254
+
255
+ - `.extend_bundle(bundle: RpcBundle)` — merges external action/stream definitions (e.g., from state-machine or storage).
256
+ - `.finalize()` — registers routes, emits .proto, and makes publishers available.
257
+
258
+ **Attributes:**
259
+
260
+ - `connect_router`: internal FastAPI router for Connect RPCs.
261
+ - `proto_path`: location of the emitted .proto.
262
+
263
+ ### Decorators
264
+
265
+ ```python
266
+ @action(name: Optional[str] = None)
267
+ # → Registers a request-response handler
268
+
269
+ @stream(
270
+ name: str,
271
+ payload: type,
272
+ replay: bool = True,
273
+ queue_maxsize: int = 1,
274
+ policy: Literal["latest", "fifo"] = "latest"
275
+ )
276
+ # → Registers a server-streaming RPC and publisher
277
+ ```
278
+
279
+ **Stream Parameters:**
280
+
281
+ - `name`: Unique name for the stream
282
+ - `payload`: Type of data to stream (Pydantic model or JSON-serializable type)
283
+ - `replay`: Whether new subscribers receive the last value (default: `True`)
284
+ - `queue_maxsize`: Maximum items per subscriber queue (default: `1`)
285
+ - `policy`: Delivery policy when queue is full - `"latest"` drops old items, `"fifo"` waits for space (default: `"latest"`)
286
+
287
+ Type inference ensures annotations are valid. Pydantic models are expanded into message definitions in the emitted .proto.
288
+
289
+ ## 🔍 Troubleshooting & FAQ
290
+
291
+ **Q: Can I disable proto generation at runtime?**
292
+
293
+ Yes — set `emit_proto=False` in `VentionApp(...)`.
294
+
295
+ **Q: Publishing raises `KeyError: Unknown stream`.**
296
+
297
+ Ensure `app.finalize()` has been called before publishing or subscribing.
298
+
299
+ **Q: How do I integrate this with other libraries (state machine, storage, etc.)?**
300
+
301
+ Use `app.extend_bundle()` to merge additional RPC definitions before calling `.finalize()`.
302
+
@@ -0,0 +1,287 @@
1
+ # Vention Communication
2
+
3
+ A thin, FastAPI-powered RPC layer for machine-apps that exposes Connect-compatible request-response and server-streaming endpoints — plus .proto generation from Python decorators, allowing typed SDKs to be generated separately via Buf.
4
+
5
+ ## Table of Contents
6
+
7
+ - [✨ Features](#-features)
8
+ - [🧠 Concepts & Overview](#-concepts--overview)
9
+ - [⚙️ Installation & Setup](#️-installation--setup)
10
+ - [🚀 Quickstart Tutorial](#-quickstart-tutorial)
11
+ - [🛠 How-to Guides](#-how-to-guides)
12
+ - [📖 API Reference](#-api-reference)
13
+ - [🔍 Troubleshooting & FAQ](#-troubleshooting--faq)
14
+
15
+ ## ✨ Features
16
+
17
+ - **Decorator-based RPCs**: `@action()` for request-response calls, `@stream()` for server streams. Types inferred from Python type hints or Pydantic models.
18
+ - **Connect-compatible endpoints**: Works seamlessly with Connect clients — compatible with `@connectrpc/connect-web` and `connectrpc-python`.
19
+ - **FastAPI-based**: Leverages FastAPI for routing and async concurrency.
20
+ - **Single service surface**: All methods exposed under `/rpc/<package.Service>/<Method>`.
21
+ - **Automatic .proto emission**: Generates a single .proto file describing all registered RPCs and models at runtime, ready for use in Buf-based SDK generation.
22
+
23
+ ## 🧠 Concepts & Overview
24
+
25
+ ### What is an RPC?
26
+
27
+ **RPC (Remote Procedure Call)** is a way to call a function on a remote server as if it were a local function. Instead of manually crafting HTTP requests and parsing responses, you define your functions with decorators, and the library handles the network communication for you. Your client code can call `client.ping({ message: "Hello" })` and get back a response, just like calling a local function.
28
+
29
+ ### What is Connect?
30
+
31
+ **Connect** is a protocol for building APIs that combines the simplicity of REST with the type safety of gRPC. It uses Protocol Buffers (protobuf) for defining your API contract, but sends data as JSON over HTTP, making it easy to use from browsers and simple HTTP clients. Connect provides:
32
+
33
+ - **Type safety**: Your API contract is defined in a `.proto` file, ensuring clients and servers agree on the data structure
34
+ - **Error handling**: Standardized error responses that work across languages
35
+ - **Streaming**: Built-in support for server-side streaming (continuously sending updates to clients)
36
+ - **Developer experience**: Works with standard HTTP tools and browsers
37
+
38
+ ### Core Concepts
39
+
40
+ - **Actions (Request-Response)** — send a request, get a response back. Input and output types are inferred from function annotations. If either is missing, `google.protobuf.Empty` is used.
41
+ - **Streams (Server streaming)** — continuous updates broadcast to all subscribers. Each stream can optionally replay the last value when someone subscribes. Queues default to size-1 to always show the latest value.
42
+ - **Service Surface** — all actions and streams belong to one service, e.g. `vention.app.v1.<YourAppName>Service`, with routes mounted under `/rpc`.
43
+ - **Proto Generation** — `VentionApp.finalize()` writes a .proto to disk, capturing all decorated RPCs, inferred models, and scalar wrappers. SDK generation (via Buf) is handled externally.
44
+
45
+ ### Stream Configuration Options
46
+
47
+ When creating a stream with `@stream()`, you can configure how updates are delivered to subscribers:
48
+
49
+ #### `replay` (default: `True`)
50
+
51
+ Controls whether new subscribers receive the last published value immediately when they subscribe.
52
+
53
+ - **`replay=True`**: New subscribers instantly receive the most recent value (if one exists). Useful for state streams where clients need the current state immediately upon connection.
54
+ - **`replay=False`**: New subscribers only receive values published after they subscribe. Useful for event streams where you only want to see new events.
55
+
56
+
57
+ #### `queue_maxsize` (default: `1`)
58
+
59
+ The maximum number of items that can be queued for each subscriber before the delivery policy kicks in.
60
+
61
+ - **`queue_maxsize=1`**: Only the latest value is kept. Perfect for state streams where you only care about the current state.
62
+ - **`queue_maxsize=N`** (N > 1): Allows buffering up to N items. Useful when subscribers might process items slower than they're published, but you still want to limit memory usage.
63
+
64
+ ```python
65
+ # Only keep latest temperature reading
66
+ @stream(name="Temperature", payload=Temperature, queue_maxsize=1)
67
+
68
+ # Buffer up to 10 sensor readings
69
+ @stream(name="SensorData", payload=SensorReading, queue_maxsize=10)
70
+ ```
71
+
72
+ #### `policy` (default: `"latest"`)
73
+
74
+ Controls what happens when a subscriber's queue is full and a new value is published.
75
+
76
+ - **`policy="latest"`**: Drops the oldest item and adds the new one. Ensures subscribers always have the most recent data. Best for state streams where you only care about the current value.
77
+ - **`policy="fifo"`**: Waits for the subscriber to process items and make space in the queue before adding new items. Ensures no data is lost but may cause backpressure if subscribers are slow. Best for event streams where you need to process every event.
78
+
79
+ ```python
80
+ # Latest-wins: always show current state
81
+ @stream(name="Status", payload=Status, policy="latest", queue_maxsize=1)
82
+
83
+ # FIFO: process every event, even if slow
84
+ @stream(name="Events", payload=Event, policy="fifo", queue_maxsize=100)
85
+ ```
86
+
87
+ **Common Combinations:**
88
+
89
+ - **State monitoring** (default): `replay=True`, `queue_maxsize=1`, `policy="latest"` — subscribers get current state immediately and always see the latest value.
90
+ - **Event streaming**: `replay=False`, `queue_maxsize=100`, `policy="fifo"` — subscribers only see new events and process them in order.
91
+
92
+ ## ⚙️ Installation & Setup
93
+
94
+ **Requirements:**
95
+
96
+ - Python 3.10+
97
+ - FastAPI
98
+ - Uvicorn (for serving)
99
+
100
+ **Install:**
101
+
102
+ ```bash
103
+ pip install vention-communication
104
+ ```
105
+
106
+ **Optional client libraries:**
107
+
108
+ - TypeScript: `@connectrpc/connect-web`
109
+ - Python: `connectrpc` with `httpx.AsyncClient`
110
+
111
+ ## 🚀 Quickstart Tutorial
112
+
113
+ ### 1. Define your RPCs
114
+
115
+ ```python
116
+ # demo/main.py
117
+ from pydantic import BaseModel
118
+
119
+ from vention_communication.app import VentionApp
120
+ from vention_communication.decorators import action, stream
121
+
122
+
123
+ class PingRequest(BaseModel):
124
+ message: str
125
+
126
+
127
+ class PingResponse(BaseModel):
128
+ message: str
129
+
130
+
131
+ class Heartbeat(BaseModel):
132
+ value: str
133
+ timestamp: int
134
+
135
+
136
+ app = VentionApp(name="DemoApp", emit_proto=True, proto_path="proto/app.proto")
137
+
138
+
139
+ @action()
140
+ async def ping(req: PingRequest) -> PingResponse:
141
+ return PingResponse(message=f"Pong: {req.message}")
142
+
143
+
144
+ @stream(name="Heartbeat", payload=Heartbeat)
145
+ async def heartbeat_publisher() -> Heartbeat:
146
+ from time import time
147
+ import random
148
+ return Heartbeat(value=f"{random.random():.2f}", timestamp=int(time()))
149
+
150
+
151
+ app.finalize()
152
+ ```
153
+
154
+ **Run:**
155
+
156
+ ```bash
157
+ uvicorn demo.main:app --reload
158
+ ```
159
+
160
+ Routes are exposed under:
161
+
162
+ - `/rpc/vention.app.v1.DemoAppService/Ping`
163
+ - `/rpc/vention.app.v1.DemoAppService/Heartbeat`
164
+
165
+ ### 2. Generated .proto
166
+
167
+ After startup, `proto/app.proto` is emitted automatically.
168
+
169
+ You can now use Buf or protoc to generate client SDKs:
170
+
171
+ ```bash
172
+ buf generate --template buf.gen.ts.yaml
173
+ buf generate --template buf.gen.python.yaml
174
+ ```
175
+
176
+ SDK generation is external to vention-communication — allowing you to control versions and plugins.
177
+
178
+ ### 3. Example TypeScript Client
179
+
180
+ ```typescript
181
+ import { createClient } from "@connectrpc/connect";
182
+ import { createConnectTransport } from "@connectrpc/connect-web";
183
+ import { DemoAppService } from "./gen/connect/proto/app_connect";
184
+
185
+ const transport = createConnectTransport({
186
+ baseUrl: "http://localhost:8000/rpc",
187
+ useBinaryFormat: false,
188
+ });
189
+
190
+ const client = createClient(DemoAppService, transport);
191
+
192
+ const res = await client.ping({ message: "Hello" });
193
+ console.log(res.message);
194
+
195
+ for await (const hb of client.heartbeat({})) {
196
+ console.log("Heartbeat", hb.value, hb.timestamp);
197
+ }
198
+ ```
199
+
200
+ ## 🛠 How-to Guides
201
+
202
+ ### Add a new request-response endpoint
203
+
204
+ ```python
205
+ @action()
206
+ async def get_status() -> dict:
207
+ return {"ok": True}
208
+ ```
209
+
210
+ ### Add a new stream
211
+
212
+ ```python
213
+ @stream(name="Status", payload=dict)
214
+ async def publish_status() -> dict:
215
+ return {"ok": True}
216
+ ```
217
+
218
+ ### Emit proto to a custom path
219
+
220
+ ```python
221
+ app = VentionApp(name="MyService", emit_proto=True, proto_path="out/myservice.proto")
222
+ app.finalize()
223
+ ```
224
+
225
+ ## 📖 API Reference
226
+
227
+ ### VentionApp
228
+
229
+ ```python
230
+ VentionApp(
231
+ name: str = "VentionApp",
232
+ *,
233
+ emit_proto: bool = False,
234
+ proto_path: str = "proto/app.proto",
235
+ **fastapi_kwargs
236
+ )
237
+ ```
238
+
239
+ **Methods:**
240
+
241
+ - `.extend_bundle(bundle: RpcBundle)` — merges external action/stream definitions (e.g., from state-machine or storage).
242
+ - `.finalize()` — registers routes, emits .proto, and makes publishers available.
243
+
244
+ **Attributes:**
245
+
246
+ - `connect_router`: internal FastAPI router for Connect RPCs.
247
+ - `proto_path`: location of the emitted .proto.
248
+
249
+ ### Decorators
250
+
251
+ ```python
252
+ @action(name: Optional[str] = None)
253
+ # → Registers a request-response handler
254
+
255
+ @stream(
256
+ name: str,
257
+ payload: type,
258
+ replay: bool = True,
259
+ queue_maxsize: int = 1,
260
+ policy: Literal["latest", "fifo"] = "latest"
261
+ )
262
+ # → Registers a server-streaming RPC and publisher
263
+ ```
264
+
265
+ **Stream Parameters:**
266
+
267
+ - `name`: Unique name for the stream
268
+ - `payload`: Type of data to stream (Pydantic model or JSON-serializable type)
269
+ - `replay`: Whether new subscribers receive the last value (default: `True`)
270
+ - `queue_maxsize`: Maximum items per subscriber queue (default: `1`)
271
+ - `policy`: Delivery policy when queue is full - `"latest"` drops old items, `"fifo"` waits for space (default: `"latest"`)
272
+
273
+ Type inference ensures annotations are valid. Pydantic models are expanded into message definitions in the emitted .proto.
274
+
275
+ ## 🔍 Troubleshooting & FAQ
276
+
277
+ **Q: Can I disable proto generation at runtime?**
278
+
279
+ Yes — set `emit_proto=False` in `VentionApp(...)`.
280
+
281
+ **Q: Publishing raises `KeyError: Unknown stream`.**
282
+
283
+ Ensure `app.finalize()` has been called before publishing or subscribing.
284
+
285
+ **Q: How do I integrate this with other libraries (state machine, storage, etc.)?**
286
+
287
+ Use `app.extend_bundle()` to merge additional RPC definitions before calling `.finalize()`.
@@ -0,0 +1,17 @@
1
+ [tool.poetry]
2
+ name = "vention-communication"
3
+ version = "0.1.0"
4
+ description = "A framework for storing and managing component and application data for machine apps."
5
+ authors = [ "VentionCo" ]
6
+ readme = "README.md"
7
+ license = "Proprietary"
8
+ packages = [{ include = "communication", from = "src" }]
9
+
10
+ [tool.poetry.dependencies]
11
+ python = ">=3.10,<3.11"
12
+ fastapi = "^0.116.1"
13
+ uvicorn = "^0.35.0"
14
+
15
+ [tool.poetry.group.dev.dependencies]
16
+ pytest = "^8.3.4"
17
+ ruff = "^0.8.0"
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+ from typing import Any, List
3
+
4
+ from fastapi import FastAPI
5
+
6
+ from .connect_router import ConnectRouter
7
+ from .decorators import collect_bundle, set_global_app
8
+ from .codegen import generate_proto, sanitize_service_name
9
+ from .entries import RpcBundle
10
+
11
+
12
+ class VentionApp(FastAPI):
13
+ """
14
+ FastAPI app that registers Connect-style RPCs and streams from decorators.
15
+ Can be extended with external RpcBundles (state-machine, storage, etc.).
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ name: str = "VentionApp",
21
+ *,
22
+ emit_proto: bool = False,
23
+ proto_path: str = "proto/app.proto",
24
+ **kwargs: Any,
25
+ ) -> None:
26
+ """Initialize the VentionApp.
27
+
28
+ Args:
29
+ name: Application name, also used as service_name for proto generation
30
+ emit_proto: Whether to emit protocol buffer definitions
31
+ proto_path: Path where proto definitions will be written
32
+ **kwargs: Additional arguments passed to FastAPI
33
+ """
34
+ super().__init__(**kwargs)
35
+ self.name = name
36
+ self.emit_proto = emit_proto
37
+ self.proto_path = proto_path
38
+ self.connect_router = ConnectRouter()
39
+ self._extra_bundles: List[RpcBundle] = []
40
+
41
+ def extend_bundle(self, bundle: RpcBundle) -> None:
42
+ """Add RPCs/streams provided by external libraries.
43
+
44
+ Must be called before finalize().
45
+
46
+ Args:
47
+ bundle: RPC bundle containing actions and streams to register
48
+ """
49
+ self._extra_bundles.append(bundle)
50
+
51
+ def finalize(self) -> None:
52
+ """Finalize the app by registering all RPCs and streams.
53
+
54
+ Collects decorator-registered RPCs, merges external bundles,
55
+ registers them with the Connect router, optionally emits proto
56
+ definitions, and makes the app available to stream publishers.
57
+ """
58
+ bundle = collect_bundle()
59
+ for extra_bundle in self._extra_bundles:
60
+ bundle.extend(extra_bundle)
61
+
62
+ service_fully_qualified_name = f"vention.app.v1.{self.service_name}Service"
63
+
64
+ for action_entry in bundle.actions:
65
+ self.connect_router.add_unary(action_entry, service_fully_qualified_name)
66
+ for stream_entry in bundle.streams:
67
+ self.connect_router.add_stream(stream_entry, service_fully_qualified_name)
68
+
69
+ self.include_router(self.connect_router.router, prefix="/rpc")
70
+
71
+ if self.emit_proto:
72
+ proto = generate_proto(self.service_name, bundle=bundle)
73
+ import os
74
+
75
+ os.makedirs(os.path.dirname(self.proto_path), exist_ok=True)
76
+ with open(self.proto_path, "w", encoding="utf-8") as proto_file:
77
+ proto_file.write(proto)
78
+
79
+ set_global_app(self)
80
+
81
+ @property
82
+ def service_name(self) -> str:
83
+ """Get the sanitized service name derived from the app name.
84
+
85
+ Returns:
86
+ Sanitized service name suitable for proto generation
87
+ """
88
+ return sanitize_service_name(self.name)