vention-communication 0.1.0__py3-none-any.whl → 0.2.0__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.
communication/app.py CHANGED
@@ -38,7 +38,7 @@ class VentionApp(FastAPI):
38
38
  self.connect_router = ConnectRouter()
39
39
  self._extra_bundles: List[RpcBundle] = []
40
40
 
41
- def extend_bundle(self, bundle: RpcBundle) -> None:
41
+ def register_rpc_plugin(self, bundle: RpcBundle) -> None:
42
42
  """Add RPCs/streams provided by external libraries.
43
43
 
44
44
  Must be called before finalize().
communication/codegen.py CHANGED
@@ -21,19 +21,9 @@ import "google/protobuf/empty.proto";
21
21
 
22
22
 
23
23
  def _unwrap_optional(type_annotation: Any) -> tuple[Any, bool]:
24
- """Unwrap Optional[T] or Union[T, None] to T.
25
-
26
- Args:
27
- type_annotation: Type annotation that may be Optional
28
-
29
- Returns:
30
- Tuple of (inner_type, is_optional)
31
- """
32
24
  origin = get_origin(type_annotation)
33
- # Check if it's a Union type (Optional is Union[T, None])
34
25
  if origin is Union:
35
26
  args = get_args(type_annotation)
36
- # Filter out NoneType
37
27
  non_none_args = [arg for arg in args if arg is not type(None)]
38
28
  if len(non_none_args) == 1:
39
29
  return (non_none_args[0], True)
@@ -41,14 +31,6 @@ def _unwrap_optional(type_annotation: Any) -> tuple[Any, bool]:
41
31
 
42
32
 
43
33
  def _unwrap_list(type_annotation: Any) -> tuple[Any, bool]:
44
- """Unwrap List[T] or list[T] to T.
45
-
46
- Args:
47
- type_annotation: Type annotation that may be a list
48
-
49
- Returns:
50
- Tuple of (inner_type, is_list)
51
- """
52
34
  origin = get_origin(type_annotation)
53
35
  if origin in (list, List):
54
36
  args = get_args(type_annotation)
@@ -58,14 +40,6 @@ def _unwrap_list(type_annotation: Any) -> tuple[Any, bool]:
58
40
 
59
41
 
60
42
  def _msg_name_for_scalar_stream(stream_name: str) -> str:
61
- """Generate a message name for a scalar stream payload.
62
-
63
- Args:
64
- stream_name: Name of the stream
65
-
66
- Returns:
67
- Message name for the scalar wrapper
68
- """
69
43
  return f"{stream_name}Message"
70
44
 
71
45
 
@@ -74,19 +48,6 @@ def _determine_proto_type_for_field(
74
48
  seen_models: set[str],
75
49
  lines: list[str],
76
50
  ) -> str:
77
- """Determine the proto type name for a field's inner type.
78
-
79
- Handles scalars, nested Pydantic models, and fallback to string.
80
- Recursively generates nested model definitions if needed.
81
-
82
- Args:
83
- inner_type: The unwrapped inner type (after Optional/List)
84
- seen_models: Set of already-seen model names to avoid duplicates
85
- lines: List to append nested message definitions to
86
-
87
- Returns:
88
- Proto type name string
89
- """
90
51
  if inner_type in _SCALAR_MAP:
91
52
  return _SCALAR_MAP[inner_type]
92
53
 
@@ -109,28 +70,11 @@ def _process_pydantic_field(
109
70
  seen_models: set[str],
110
71
  lines: list[str],
111
72
  ) -> str:
112
- """Process a single Pydantic field and generate its proto definition.
113
-
114
- Args:
115
- field_name: Name of the field
116
- field_type: Type annotation of the field
117
- field_index: Proto field number (1-indexed)
118
- seen_models: Set of already-seen model names to avoid duplicates
119
- lines: List to append nested message definitions to
120
-
121
- Returns:
122
- Proto field definition line (e.g., " string name = 1;")
123
- """
124
- # Unwrap Optional first
125
73
  inner_type, _ = _unwrap_optional(field_type)
126
-
127
- # Unwrap List
128
74
  list_inner_type, is_list = _unwrap_list(inner_type)
129
75
 
130
- # Determine proto type (handles nested models recursively)
131
76
  proto_type = _determine_proto_type_for_field(list_inner_type, seen_models, lines)
132
77
 
133
- # Add "repeated" prefix for lists
134
78
  if is_list:
135
79
  proto_type = f"repeated {proto_type}"
136
80
 
@@ -142,16 +86,6 @@ def _generate_pydantic_message(
142
86
  seen_models: set[str],
143
87
  lines: list[str],
144
88
  ) -> list[str]:
145
- """Generate proto message definition for a Pydantic model.
146
-
147
- Args:
148
- type_annotation: Pydantic model type
149
- seen_models: Set of already-seen model names to avoid duplicates
150
- lines: List to append nested message definitions to
151
-
152
- Returns:
153
- List of lines for the message definition
154
- """
155
89
  model_name = type_annotation.__name__
156
90
  fields = []
157
91
  field_index = 1
@@ -176,15 +110,6 @@ def _generate_pydantic_message(
176
110
  def _generate_scalar_wrapper_message(
177
111
  stream_name: str, payload_type: Type[Any]
178
112
  ) -> list[str]:
179
- """Generate proto message definition for a scalar stream payload.
180
-
181
- Args:
182
- stream_name: Name of the stream
183
- payload_type: Scalar type (int, float, str, bool)
184
-
185
- Returns:
186
- List of lines for the wrapper message definition
187
- """
188
113
  wrapper_name = _msg_name_for_scalar_stream(stream_name)
189
114
  lines = [
190
115
  f"message {wrapper_name} {{",
@@ -199,30 +124,16 @@ def _proto_type_name(
199
124
  scalar_wrappers: Dict[str, str],
200
125
  stream_name: Optional[str] = None,
201
126
  ) -> str:
202
- """Map a Python type to its proto type name.
203
-
204
- Args:
205
- type_annotation: Type to map
206
- scalar_wrappers: Dictionary mapping stream names to wrapper message names
207
- stream_name: Optional stream name for scalar wrapper lookup
208
-
209
- Returns:
210
- Proto type name string
211
- """
212
127
  if type_annotation is None:
213
128
  return "google.protobuf.Empty"
214
129
 
215
- # Unwrap Optional
216
130
  inner_type, _ = _unwrap_optional(type_annotation)
217
131
 
218
- # Unwrap List (for streams, lists become scalar wrappers)
219
132
  list_inner_type, is_list = _unwrap_list(inner_type)
220
133
 
221
134
  if list_inner_type in _SCALAR_MAP:
222
135
  if stream_name and stream_name in scalar_wrappers:
223
136
  return scalar_wrappers[stream_name]
224
- # For streams, if it's a list, we don't use "repeated" in the return type
225
- # The stream itself provides the repetition
226
137
  return str(_SCALAR_MAP[list_inner_type])
227
138
 
228
139
  if is_pydantic_model(list_inner_type):
@@ -236,30 +147,17 @@ def _register_pydantic_model(
236
147
  seen_models: set[str],
237
148
  lines: list[str],
238
149
  ) -> None:
239
- """Register a Pydantic model and recursively register nested models.
240
-
241
- Unwraps Optional and List types to find the underlying Pydantic model,
242
- then generates its proto definition if not already seen.
243
-
244
- Args:
245
- type_annotation: Type annotation that may contain a Pydantic model
246
- seen_models: Set of already-seen model names to avoid duplicates
247
- lines: List to append message definitions to
248
- """
249
150
  if type_annotation is None:
250
151
  return
251
152
 
252
- # Unwrap Optional
253
153
  inner_type, _ = _unwrap_optional(type_annotation)
254
154
 
255
- # Unwrap List to get inner type
256
155
  list_inner_type, _ = _unwrap_list(inner_type)
257
156
 
258
157
  if is_pydantic_model(list_inner_type):
259
158
  model_name = list_inner_type.__name__
260
159
  if model_name not in seen_models:
261
160
  seen_models.add(model_name)
262
- # This will recursively register nested models via _generate_pydantic_message
263
161
  lines.extend(
264
162
  _generate_pydantic_message(list_inner_type, seen_models, lines)
265
163
  )
@@ -271,15 +169,6 @@ def _process_stream_payload(
271
169
  lines: list[str],
272
170
  scalar_wrappers: Dict[str, str],
273
171
  ) -> None:
274
- """Process a stream's payload type and generate necessary message definitions.
275
-
276
- Args:
277
- stream_entry: Stream entry containing payload type information
278
- seen_models: Set of already-seen model names to avoid duplicates
279
- lines: List to append message definitions to
280
- scalar_wrappers: Dictionary to populate with scalar wrapper mappings
281
- """
282
- # Unwrap Optional and List for stream payloads
283
172
  inner_type, _ = _unwrap_optional(stream_entry.payload_type)
284
173
  list_inner_type, _ = _unwrap_list(inner_type)
285
174
 
@@ -299,20 +188,10 @@ def _collect_message_types(
299
188
  seen_models: set[str],
300
189
  scalar_wrappers: Dict[str, str],
301
190
  ) -> None:
302
- """Collect and generate all message types needed for the proto.
303
-
304
- Args:
305
- bundle: RPC bundle containing actions and streams
306
- lines: List to append message definitions to
307
- seen_models: Set of already-seen model names to avoid duplicates
308
- scalar_wrappers: Dictionary to populate with scalar wrapper mappings
309
- """
310
- # Process action input/output types
311
191
  for action_entry in bundle.actions:
312
192
  _register_pydantic_model(action_entry.input_type, seen_models, lines)
313
193
  _register_pydantic_model(action_entry.output_type, seen_models, lines)
314
194
 
315
- # Process stream payload types
316
195
  for stream_entry in bundle.streams:
317
196
  _process_stream_payload(stream_entry, seen_models, lines, scalar_wrappers)
318
197
 
@@ -320,13 +199,6 @@ def _collect_message_types(
320
199
  def _generate_service_rpcs(
321
200
  bundle: RpcBundle, lines: list[str], scalar_wrappers: Dict[str, str]
322
201
  ) -> None:
323
- """Generate RPC method definitions for actions and streams.
324
-
325
- Args:
326
- bundle: RPC bundle containing actions and streams
327
- lines: List to append RPC definitions to
328
- scalar_wrappers: Dictionary mapping stream names to wrapper message names
329
- """
330
202
  rpc_prefix = " rpc"
331
203
 
332
204
  for action_entry in bundle.actions:
@@ -346,15 +218,6 @@ def _generate_service_rpcs(
346
218
 
347
219
 
348
220
  def generate_proto(app_name: str, *, bundle: Optional[RpcBundle] = None) -> str:
349
- """Generate a Protocol Buffer definition from RPC actions and streams.
350
-
351
- Args:
352
- app_name: Name of the application (used for service name)
353
- bundle: Optional RPC bundle. If not provided, collects from decorators.
354
-
355
- Returns:
356
- Complete proto3 file content as a string
357
- """
358
221
  if bundle is None:
359
222
  bundle = collect_bundle()
360
223
 
@@ -373,17 +236,6 @@ def generate_proto(app_name: str, *, bundle: Optional[RpcBundle] = None) -> str:
373
236
 
374
237
 
375
238
  def sanitize_service_name(name: str) -> str:
376
- """Sanitize an application name for use as a service name.
377
-
378
- Extracts alphanumeric parts, capitalizes each part, and joins them.
379
- Invalid characters are dropped.
380
-
381
- Args:
382
- name: Application name to sanitize
383
-
384
- Returns:
385
- Sanitized service name, or "VentionApp" if no valid parts found
386
- """
387
239
  import re
388
240
 
389
241
  parts = re.findall(r"[A-Za-z0-9]+", name)
@@ -18,14 +18,6 @@ CONTENT_TYPE = "application/connect+json"
18
18
 
19
19
 
20
20
  def _frame(payload: Dict[str, Any], *, trailer: bool = False) -> bytes:
21
- """Create a Connect protocol frame for streaming responses.
22
-
23
- Connect uses a binary framing format for streaming JSON over HTTP. Each frame
24
- consists of:
25
- - 1 byte: Flags (0x00 for data, 0x80 for trailer/end-of-stream)
26
- - 4 bytes: Body length in big-endian format
27
- - N bytes: JSON-encoded payload
28
- """
29
21
  body = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode(
30
22
  "utf-8"
31
23
  )
@@ -52,7 +44,6 @@ class StreamManager:
52
44
  """
53
45
 
54
46
  def __init__(self) -> None:
55
- """Initialize the StreamManager with an empty topic registry."""
56
47
  self._topics: Dict[str, Dict[str, Any]] = {}
57
48
 
58
49
  def ensure_topic(self, entry: StreamEntry) -> None:
@@ -157,14 +148,6 @@ class StreamManager:
157
148
  topic["subscribers"].discard(subscriber)
158
149
 
159
150
  async def _distributor(self, stream_name: str) -> None:
160
- """Distribute published items to all subscribers of a stream.
161
-
162
- For FIFO policy, waits for queue space. For latest-wins policy,
163
- drops oldest items when queues are full.
164
-
165
- Args:
166
- stream_name: Name of the stream topic to distribute
167
- """
168
151
  topic = self._topics[stream_name]
169
152
  publish_queue: asyncio.Queue[Any] = topic["publish_queue"]
170
153
  subscribers: set[_Subscriber] = topic["subscribers"]
@@ -312,14 +295,6 @@ def _serialize_stream_item(item: Any) -> Dict[str, Any]:
312
295
 
313
296
 
314
297
  async def _maybe_await(value: Any) -> Any:
315
- """Await a value if it's a coroutine or Future, otherwise return it.
316
-
317
- Args:
318
- value: Value that may or may not be awaitable
319
-
320
- Returns:
321
- The result of awaiting or the value itself
322
- """
323
298
  if asyncio.iscoroutine(value) or isinstance(value, asyncio.Future):
324
299
  return await value
325
300
  return value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vention-communication
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A framework for storing and managing component and application data for machine apps.
5
5
  License: Proprietary
6
6
  Author: VentionCo
@@ -28,26 +28,31 @@ A thin, FastAPI-powered RPC layer for machine-apps that exposes Connect-compatib
28
28
 
29
29
  ## ✨ Features
30
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.
31
+ - **Zero boilerplate RPCs**: Expose any async Python function as a network API with a single decorator.
32
+ - **Strong typing**: Request and response models derived directly from Python annotations.
33
+ - **Built-in schema emission**: Generates a .proto file at runtime, ready for SDK code generation.
34
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.
35
+ - **Connect-compatible transport**: Works seamlessly with `@connectrpc/connect-web` and `connectrpc-python`.
36
36
 
37
37
  ## 🧠 Concepts & Overview
38
38
 
39
- ### What is an RPC?
39
+ ### The Problem
40
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.
41
+ Machine-app developers write Python daily but shouldn’t need to know REST, gRPC, or frontend networking.
42
+ They just need a way to say “this function should be callable from anywhere.”
42
43
 
43
- ### What is Connect?
44
+ ### The Solution
44
45
 
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
+ vention-communication bridges that gap by turning annotated Python functions into typed RPC endpoints automatically.
46
47
 
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
48
+ `@action()` defines a one-request / one-response method.
49
+
50
+ `@stream()` defines a live telemetry or event stream that other services can subscribe to.
51
+
52
+ `VentionApp.finalize()` → scans for decorators, builds a Connect router, and emits a .proto schema.
53
+
54
+ Once the .proto exists, SDKs for TypeScript, Python, or Go can be generated using Buf.
55
+ The result: your frontend gets auto-completed methods for every RPC, no HTTP or JSON code required.
51
56
 
52
57
  ### Core Concepts
53
58
 
@@ -56,52 +61,6 @@ A thin, FastAPI-powered RPC layer for machine-apps that exposes Connect-compatib
56
61
  - **Service Surface** — all actions and streams belong to one service, e.g. `vention.app.v1.<YourAppName>Service`, with routes mounted under `/rpc`.
57
62
  - **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
63
 
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
64
 
106
65
  ## ⚙️ Installation & Setup
107
66
 
@@ -123,46 +82,38 @@ pip install vention-communication
123
82
  - Python: `connectrpc` with `httpx.AsyncClient`
124
83
 
125
84
  ## 🚀 Quickstart Tutorial
85
+ A complete "hello world" in three steps.
126
86
 
127
87
  ### 1. Define your RPCs
128
88
 
129
89
  ```python
130
- # demo/main.py
131
90
  from pydantic import BaseModel
132
-
133
- from vention_communication.app import VentionApp
134
- from vention_communication.decorators import action, stream
135
-
91
+ from vention_communication import VentionApp, action, stream
92
+ import time, random
136
93
 
137
94
  class PingRequest(BaseModel):
138
95
  message: str
139
96
 
140
-
141
97
  class PingResponse(BaseModel):
142
98
  message: str
143
99
 
144
-
145
100
  class Heartbeat(BaseModel):
146
101
  value: str
147
102
  timestamp: int
148
103
 
149
-
150
- app = VentionApp(name="DemoApp", emit_proto=True, proto_path="proto/app.proto")
151
-
104
+ app = VentionApp(name="DemoApp", emit_proto=True)
152
105
 
153
106
  @action()
154
107
  async def ping(req: PingRequest) -> PingResponse:
155
108
  return PingResponse(message=f"Pong: {req.message}")
156
109
 
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
-
110
+ @stream(name="heartbeat", payload=Heartbeat, replay=True)
111
+ async def heartbeat():
112
+ """Broadcast a live heartbeat value to all subscribers."""
113
+ return Heartbeat(value=f"{random.uniform(0,100):.2f}", timestamp=int(time.time()))
164
114
 
165
115
  app.finalize()
116
+
166
117
  ```
167
118
 
168
119
  **Run:**
@@ -171,10 +122,7 @@ app.finalize()
171
122
  uvicorn demo.main:app --reload
172
123
  ```
173
124
 
174
- Routes are exposed under:
175
-
176
- - `/rpc/vention.app.v1.DemoAppService/Ping`
177
- - `/rpc/vention.app.v1.DemoAppService/Heartbeat`
125
+ Endpoints are automatically registered under `/rpc/vention.app.v1.DemoAppService.`
178
126
 
179
127
  ### 2. Generated .proto
180
128
 
@@ -252,7 +200,7 @@ VentionApp(
252
200
 
253
201
  **Methods:**
254
202
 
255
- - `.extend_bundle(bundle: RpcBundle)` — merges external action/stream definitions (e.g., from state-machine or storage).
203
+ - `.register_rpc_plugin(bundle: RpcBundle)` — merges external action/stream definitions (e.g., from state-machine or storage).
256
204
  - `.finalize()` — registers routes, emits .proto, and makes publishers available.
257
205
 
258
206
  **Attributes:**
@@ -284,7 +232,90 @@ VentionApp(
284
232
  - `queue_maxsize`: Maximum items per subscriber queue (default: `1`)
285
233
  - `policy`: Delivery policy when queue is full - `"latest"` drops old items, `"fifo"` waits for space (default: `"latest"`)
286
234
 
287
- Type inference ensures annotations are valid. Pydantic models are expanded into message definitions in the emitted .proto.
235
+ ### Stream Configuration Options
236
+
237
+ When creating a stream with `@stream()`, you can configure how updates are delivered to subscribers:
238
+
239
+ #### `replay` (default: `True`)
240
+
241
+ Controls whether new subscribers receive the last published value immediately when they subscribe.
242
+
243
+ - **`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.
244
+ - **`replay=False`**: New subscribers only receive values published after they subscribe. Useful for event streams where you only want to see new events.
245
+
246
+
247
+ #### `queue_maxsize` (default: `1`)
248
+
249
+ The maximum number of items that can be queued for each subscriber before the delivery policy kicks in.
250
+
251
+ - **`queue_maxsize=1`**: Only the latest value is kept. Perfect for state streams where you only care about the current state.
252
+ - **`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.
253
+
254
+ ```python
255
+ # Only keep latest temperature reading
256
+ @stream(name="Temperature", payload=Temperature, queue_maxsize=1)
257
+
258
+ # Buffer up to 10 sensor readings
259
+ @stream(name="SensorData", payload=SensorReading, queue_maxsize=10)
260
+ ```
261
+
262
+ #### `policy` (default: `"latest"`)
263
+
264
+ Defines what happens when a subscriber’s queue is full and a new value is published.
265
+
266
+ Each subscriber maintains its own in-memory queue of pending messages.
267
+ When you publish faster than a client can consume, the queue eventually fills — the policy determines what happens next.
268
+
269
+ `policy="latest"` — “drop oldest, never block”
270
+
271
+ - The publisher never waits.
272
+ - If a subscriber’s queue is full, the oldest item is dropped and the new one is inserted immediately.
273
+ - Fast subscribers receive every message; slow subscribers skip intermediate values but always see the most recent state.
274
+
275
+ ✅ Pros
276
+ - Zero backpressure — publisher performance unaffected by slow clients.
277
+ - Keeps UI dashboards and telemetry feeds current (“latest value always wins”).
278
+ - Ideal for high-frequency data (positions, sensor readings, machine state).
279
+
280
+ ⚠️ Cons
281
+ - Drops messages for slow clients (they may miss intermediate updates).
282
+ - Subscribers can diverge — one may receive more updates than another.
283
+
284
+ Example:
285
+ ```python
286
+ @stream(name="Temperature", payload=TempReading,
287
+ policy="latest", queue_maxsize=1)
288
+ # → publisher never blocks; subscribers always see the most recent temperature
289
+ ```
290
+ `policy="fifo"` — “deliver all, may block”
291
+
292
+ - The publisher awaits until there’s space in every subscriber’s queue.
293
+ - Guarantees that all messages are delivered in order to every subscriber.
294
+ - A slow subscriber can stall the entire stream, because the distributor
295
+ waits for that subscriber’s queue to make room before continuing.
296
+
297
+ ✅ Pros
298
+ - Preserves every event and strict ordering.
299
+ - Reliable for command sequences, audit logs, and event-driven logic.
300
+
301
+ ⚠️ Cons
302
+ - One slow or paused subscriber can block all others.
303
+ - Publishing rate is limited by the slowest client.
304
+ - In extreme cases, a throttled browser or dropped connection can cause the
305
+ distributor to stall until the queue frees or the subscriber is removed.
306
+
307
+ Example:
308
+ ```python
309
+ @stream(name="Events", payload=MachineEvent,
310
+ policy="fifo", queue_maxsize=100)
311
+ # → guarantees ordered delivery but can back-pressure the publisher
312
+ ```
313
+
314
+ **Common Combinations:**
315
+
316
+ - **State monitoring** (default): `replay=True`, `queue_maxsize=1`, `policy="latest"` — subscribers get current state immediately and always see the latest value.
317
+ - **Event streaming**: `replay=False`, `queue_maxsize=100`, `policy="fifo"` — subscribers only see new events and process them in order.
318
+
288
319
 
289
320
  ## 🔍 Troubleshooting & FAQ
290
321
 
@@ -298,5 +329,5 @@ Ensure `app.finalize()` has been called before publishing or subscribing.
298
329
 
299
330
  **Q: How do I integrate this with other libraries (state machine, storage, etc.)?**
300
331
 
301
- Use `app.extend_bundle()` to merge additional RPC definitions before calling `.finalize()`.
332
+ Use `app.register_rpc_plugin()` to merge additional RPC definitions before calling `.finalize()`.
302
333
 
@@ -0,0 +1,11 @@
1
+ communication/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ communication/app.py,sha256=9syaxfb8HJa3x_WBWa3ZRKClBFbaOZkSL_D6L0ZwIfI,3030
3
+ communication/codegen.py,sha256=SwCIYptjmCbnFGPl0zZ0Ahj4UP27xpy7ELftQurMANM,7089
4
+ communication/connect_router.py,sha256=uwFmMKdvVUC0dwepXCTvWcERQV2QlGD1xdTkIHCYl5k,10501
5
+ communication/decorators.py,sha256=3pVlXUSX4KXSKlweskF0RfD8pST2zisTuFGJQHHIvcI,3419
6
+ communication/entries.py,sha256=vdZc8GAQztRWEiav6R2wM4l35GE-EiEdRH0ZJR4GShM,1065
7
+ communication/errors.py,sha256=hdJBB9jPJNWx8hbxIxwLBNKt2JVpmhZ1YF8q9VKk-dI,1773
8
+ communication/typing_utils.py,sha256=M_q_2fKjEG8t2de5nzmHH55CmsGtTNYbKI_AlKkFUN8,2399
9
+ vention_communication-0.2.0.dist-info/METADATA,sha256=v-bjhhD1ScpMVSk2KyuGJcR11DdWIddHTIOqeA7EmVM,11279
10
+ vention_communication-0.2.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
11
+ vention_communication-0.2.0.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- communication/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- communication/app.py,sha256=JtuC9x1ZmgckTeeAl5-w8Q-FyCyvBo3gDZW51aPvvxs,3024
3
- communication/codegen.py,sha256=y1RFRRFKn5nyh1vXqf4F3Ws0SZWXb4TviSvLRQyl2gM,12190
4
- communication/connect_router.py,sha256=mi__JurSi0qAQWMXfdVzUQK31t7RIxBtMFl76BY-MHw,11392
5
- communication/decorators.py,sha256=3pVlXUSX4KXSKlweskF0RfD8pST2zisTuFGJQHHIvcI,3419
6
- communication/entries.py,sha256=vdZc8GAQztRWEiav6R2wM4l35GE-EiEdRH0ZJR4GShM,1065
7
- communication/errors.py,sha256=hdJBB9jPJNWx8hbxIxwLBNKt2JVpmhZ1YF8q9VKk-dI,1773
8
- communication/typing_utils.py,sha256=M_q_2fKjEG8t2de5nzmHH55CmsGtTNYbKI_AlKkFUN8,2399
9
- vention_communication-0.1.0.dist-info/METADATA,sha256=q30RBOyGM9XBI35ypwBdWfq1Uxo7CFLhYmxt5B2rdv8,10575
10
- vention_communication-0.1.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
11
- vention_communication-0.1.0.dist-info/RECORD,,