istos 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.
Files changed (69) hide show
  1. istos-0.1.0/PKG-INFO +555 -0
  2. istos-0.1.0/README.md +497 -0
  3. istos-0.1.0/pyproject.toml +136 -0
  4. istos-0.1.0/src/istos/__init__.py +79 -0
  5. istos-0.1.0/src/istos/app/__init__.py +44 -0
  6. istos-0.1.0/src/istos/app/_base.py +278 -0
  7. istos-0.1.0/src/istos/app/lifecycle.py +146 -0
  8. istos-0.1.0/src/istos/app/messaging.py +553 -0
  9. istos-0.1.0/src/istos/app/queues.py +438 -0
  10. istos-0.1.0/src/istos/app/streaming.py +368 -0
  11. istos-0.1.0/src/istos/app/web.py +482 -0
  12. istos-0.1.0/src/istos/cli.py +134 -0
  13. istos-0.1.0/src/istos/communication/__init__.py +13 -0
  14. istos-0.1.0/src/istos/communication/config.py +201 -0
  15. istos-0.1.0/src/istos/communication/durable.py +133 -0
  16. istos-0.1.0/src/istos/communication/persist.py +348 -0
  17. istos-0.1.0/src/istos/communication/sessions.py +381 -0
  18. istos-0.1.0/src/istos/consistency/__init__.py +22 -0
  19. istos-0.1.0/src/istos/consistency/config.py +94 -0
  20. istos-0.1.0/src/istos/consistency/databases.py +100 -0
  21. istos-0.1.0/src/istos/consistency/redis_storage.py +100 -0
  22. istos-0.1.0/src/istos/consistency/sqlalchemy_storage.py +278 -0
  23. istos-0.1.0/src/istos/consistency/storage.py +117 -0
  24. istos-0.1.0/src/istos/context.py +116 -0
  25. istos-0.1.0/src/istos/di/__init__.py +25 -0
  26. istos-0.1.0/src/istos/di/context.py +44 -0
  27. istos-0.1.0/src/istos/di/depends.py +247 -0
  28. istos-0.1.0/src/istos/discovery/__init__.py +0 -0
  29. istos-0.1.0/src/istos/discovery/asyncapi.py +295 -0
  30. istos-0.1.0/src/istos/errors.py +157 -0
  31. istos-0.1.0/src/istos/fitness.py +398 -0
  32. istos-0.1.0/src/istos/http/__init__.py +0 -0
  33. istos-0.1.0/src/istos/http/asgi.py +46 -0
  34. istos-0.1.0/src/istos/http/gateway.py +162 -0
  35. istos-0.1.0/src/istos/http/health.py +52 -0
  36. istos-0.1.0/src/istos/http/mcp.py +99 -0
  37. istos-0.1.0/src/istos/logging.py +181 -0
  38. istos-0.1.0/src/istos/messages/__init__.py +21 -0
  39. istos-0.1.0/src/istos/messages/serialization.py +148 -0
  40. istos-0.1.0/src/istos/middleware/__init__.py +4 -0
  41. istos-0.1.0/src/istos/middleware/base.py +133 -0
  42. istos-0.1.0/src/istos/middleware/ratelimit.py +67 -0
  43. istos-0.1.0/src/istos/observability/__init__.py +9 -0
  44. istos-0.1.0/src/istos/observability/metrics.py +72 -0
  45. istos-0.1.0/src/istos/observability/tracing.py +83 -0
  46. istos-0.1.0/src/istos/primitives/__init__.py +0 -0
  47. istos-0.1.0/src/istos/primitives/channel.py +237 -0
  48. istos-0.1.0/src/istos/primitives/channel_fabric.py +242 -0
  49. istos-0.1.0/src/istos/primitives/clients.py +99 -0
  50. istos-0.1.0/src/istos/primitives/handler.py +293 -0
  51. istos-0.1.0/src/istos/primitives/liveliness.py +63 -0
  52. istos-0.1.0/src/istos/primitives/publish.py +146 -0
  53. istos-0.1.0/src/istos/primitives/query.py +154 -0
  54. istos-0.1.0/src/istos/primitives/session_store.py +29 -0
  55. istos-0.1.0/src/istos/primitives/stream.py +178 -0
  56. istos-0.1.0/src/istos/primitives/subscribe.py +289 -0
  57. istos-0.1.0/src/istos/py.typed +0 -0
  58. istos-0.1.0/src/istos/queue/__init__.py +11 -0
  59. istos-0.1.0/src/istos/queue/cron.py +95 -0
  60. istos-0.1.0/src/istos/queue/role.py +340 -0
  61. istos-0.1.0/src/istos/queue/store.py +361 -0
  62. istos-0.1.0/src/istos/queue/worker.py +187 -0
  63. istos-0.1.0/src/istos/retry.py +62 -0
  64. istos-0.1.0/src/istos/routing.py +162 -0
  65. istos-0.1.0/src/istos/security/__init__.py +0 -0
  66. istos-0.1.0/src/istos/security/authz.py +325 -0
  67. istos-0.1.0/src/istos/testing/__init__.py +3 -0
  68. istos-0.1.0/src/istos/testing/testclient.py +233 -0
  69. istos-0.1.0/src/istos/validation.py +128 -0
istos-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,555 @@
1
+ Metadata-Version: 2.4
2
+ Name: istos
3
+ Version: 0.1.0
4
+ Summary: A unified Python framework for distributed systems and multi-agent applications over Eclipse Zenoh
5
+ Keywords: zenoh,distributed-systems,pubsub,rpc,microservices,agents,iot
6
+ Author: amirrezaei
7
+ Author-email: amirrezaei <corvology@gmail.com>
8
+ License-Expression: Apache-2.0
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: Apache Software License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
19
+ Classifier: Topic :: System :: Distributed Computing
20
+ Classifier: Typing :: Typed
21
+ Requires-Dist: aiohttp>=3.13.5
22
+ Requires-Dist: eclipse-zenoh>=1.7.2
23
+ Requires-Dist: msgpack>=1.1.2
24
+ Requires-Dist: pydantic>=2.12.5
25
+ Requires-Dist: pydantic-settings>=2.13.1
26
+ Requires-Dist: pyyaml>=6.0.3
27
+ Requires-Dist: istos[redis,sqlalchemy,otel,s3,jwt] ; extra == 'all'
28
+ Requires-Dist: mypy>=1.19.1 ; extra == 'dev'
29
+ Requires-Dist: pylint>=4.0.5 ; extra == 'dev'
30
+ Requires-Dist: pytest>=9.0.2 ; extra == 'dev'
31
+ Requires-Dist: pytest-asyncio>=1.3.0 ; extra == 'dev'
32
+ Requires-Dist: pytest-mock>=3.15.1 ; extra == 'dev'
33
+ Requires-Dist: pytest-cov>=6.0.0 ; extra == 'dev'
34
+ Requires-Dist: mkdocs>=1.6.0 ; extra == 'dev'
35
+ Requires-Dist: mkdocs-material>=9.5.0 ; extra == 'dev'
36
+ Requires-Dist: mkdocstrings[python]>=0.27.0 ; extra == 'dev'
37
+ Requires-Dist: pyjwt>=2.8.0 ; extra == 'jwt'
38
+ Requires-Dist: opentelemetry-api>=1.28.0 ; extra == 'otel'
39
+ Requires-Dist: opentelemetry-sdk>=1.28.0 ; extra == 'otel'
40
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.28.0 ; extra == 'otel'
41
+ Requires-Dist: redis>=5.0.0 ; extra == 'redis'
42
+ Requires-Dist: aioboto3>=13.0.0 ; extra == 's3'
43
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.0 ; extra == 'sqlalchemy'
44
+ Requires-Python: >=3.10, <3.15
45
+ Project-URL: Homepage, https://github.com/0x416d6972/Istos
46
+ Project-URL: Documentation, https://0x416d6972.github.io/Istos/
47
+ Project-URL: Repository, https://github.com/0x416d6972/Istos
48
+ Project-URL: Issues, https://github.com/0x416d6972/Istos/issues
49
+ Project-URL: Changelog, https://github.com/0x416d6972/Istos/blob/main/docs/changelog.md
50
+ Provides-Extra: all
51
+ Provides-Extra: dev
52
+ Provides-Extra: jwt
53
+ Provides-Extra: otel
54
+ Provides-Extra: redis
55
+ Provides-Extra: s3
56
+ Provides-Extra: sqlalchemy
57
+ Description-Content-Type: text/markdown
58
+
59
+ # Istos
60
+
61
+ <p align="center">
62
+ <a href="https://pypi.org/project/istos/"><img alt="PyPI" src="https://img.shields.io/pypi/v/istos.svg"></a>
63
+ <a href="https://github.com/0x416d6972/Istos/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/0x416d6972/Istos/actions/workflows/ci.yml/badge.svg"></a>
64
+ <img alt="Python" src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue">
65
+ <img alt="License" src="https://img.shields.io/badge/license-Apache--2.0-green">
66
+ <img alt="Status" src="https://img.shields.io/badge/status-0.1.0%20Beta-orange">
67
+ </p>
68
+
69
+ <p align="center">
70
+ <em>Decorator-first Python services on <a href="https://zenoh.io/">Eclipse Zenoh</a> — RPC, streaming, duplex channels, work queues, and durable pub/sub.</em>
71
+ </p>
72
+
73
+ **Istos** is for building clean-architecture software and AI agents. A small, explicit decorator surface keeps your codebase maintainable and right-sized — modules that stay easy to analyze, debug, and develop, including with vibe coding, where the machine writes more of the code and readable, well-bounded components are what keep it under control. It maps network operations onto Python decorators so you can wire request/reply, token streams, interactive agent sessions, jobs, and events without standing up a message broker.
74
+
75
+ ---
76
+
77
+ ## Why Istos
78
+
79
+ Distributed systems are hard enough without spending your design budget on
80
+ message brokers. Istos lets you reason about *services that answer requests*
81
+ and *events that fan out* — you draw the boxes and arrows; Istos **is** the
82
+ arrows. A two-service prototype and a hundred-service fleet are the same
83
+ mental model, just more decorators.
84
+
85
+ There is no Kafka, RabbitMQ, NATS, or Redis Streams cluster to provision.
86
+ Durability lives in the *peers* — via [Eclipse Zenoh](https://zenoh.io/)'s
87
+ advanced pub/sub — not in a central log you have to operate. `pip install`,
88
+ write a decorated function, `run()`.
89
+
90
+ ---
91
+
92
+ ## Key Features
93
+
94
+ - **Decorators first**: `@handle`, `@stream`, `@channel`, `@publish`, `@subscribe`.
95
+ - **Streaming RPC**: `@stream` yields chunks; `stream_query` / `@stream_client` consume them.
96
+ - **Duplex channels**: `@channel` + `open_channel` / `@channel_client` for multi-turn agents (WebSocket or fabric).
97
+ - **HTTP gateway**: `Istos(http_port=8080)` plus `http=` / `ws=` — JSON, SSE, WebSocket. Co-host inside FastAPI with `istos.asgi.lifespan`. Optional MCP tools from `@handle`.
98
+ - **Smart selectors**: Query params like `?limit=5` land on your Python arguments.
99
+ - **Schema validation**: Type hints / Pydantic at the edge.
100
+ - **Retry policies**: `retry=5` (or a `RetryPolicy`) on queries and subscribers.
101
+ - **Brokerless durability**: `durable=True` replay caches; `persist="s3://…"` / `app.replay(...)` when the producer itself can die.
102
+ - **Security**: TLS/mTLS at the transport; `TokenAuthorizer` / `JWTAuthorizer` / `require_roles` on handlers. Default is still open — use `require_auth=True` when you mean it.
103
+ - **Pluggable storage**: in-memory, Redis, or SQLAlchemy (bring your async driver).
104
+
105
+ ## The Mental Model
106
+
107
+ - **`@handle` & `@query`**: 1-to-1 RPC
108
+ - **`@stream`**: 1-to-1 streaming RPC (SLM/LLM tokens)
109
+ - **`@channel`**: full-duplex sessions (agents)
110
+ - **`@publish` & `@subscribe`**: 1-to-many events
111
+ - **`@on_liveliness`**: node discovery & health
112
+
113
+ ## Installation
114
+
115
+ This project uses modern Python packaging via [`uv`](https://github.com/astral-sh/uv).
116
+
117
+ ```bash
118
+ # Standard installation
119
+ uv pip install istos
120
+
121
+ # Or install from source:
122
+ git clone https://github.com/0x416d6972/Istos.git
123
+ cd istos
124
+ uv pip install -e .
125
+
126
+ # Or with the optional backends (Redis, SQLAlchemy, S3 persistence, JWT, OTel):
127
+ uv pip install -e ".[all]"
128
+ ```
129
+
130
+ ## Quick Start
131
+
132
+ ### 1. Registering a Handler
133
+ Handlers sit on the network and respond to incoming queries. Istos automatically parses query parameters into your function's arguments.
134
+
135
+ ```python
136
+ from istos import Istos
137
+
138
+ istos = Istos()
139
+
140
+ @istos.handle(prefix="robot/move")
141
+ async def move(distance: int, speed: str = "normal"):
142
+ """
143
+ Called when a Zenoh Query hits 'robot/move'.
144
+ E.g., Querying 'robot/move?distance=10&speed=fast' automatically binds:
145
+ distance=10, speed='fast'
146
+ """
147
+ return {"status": "success", "distance": int(distance), "speed": speed}
148
+
149
+ if __name__ == "__main__":
150
+ # Blocks and listens for queries
151
+ istos.run()
152
+ ```
153
+
154
+ ### 2. Querying the Network
155
+ You can easily query handlers registered anywhere on the Zenoh network using `kwargs` to build Zenoh Selectors.
156
+
157
+ ```python
158
+ from contextlib import asynccontextmanager
159
+ from istos import Istos
160
+
161
+ # Using @istos.query makes it a callable network function
162
+ @istos.query("robot/move")
163
+ async def query_robot(result):
164
+ return result
165
+
166
+ # Queries run over the service's shared Zenoh session, so trigger them once the
167
+ # service is running — e.g. from a handler or a lifespan hook:
168
+ @asynccontextmanager
169
+ async def on_start(app):
170
+ reply = await query_robot(distance=15, speed="fast")
171
+ print(f"Robot replied: {reply}")
172
+ yield
173
+
174
+ istos = Istos(lifespan=on_start)
175
+
176
+ if __name__ == "__main__":
177
+ istos.run()
178
+ ```
179
+
180
+ > There is no longer a per-call "transient session" fallback: the whole service
181
+ > shares **one** Zenoh session opened by `istos.run()`/`run_async()`. Calling a
182
+ > `@query`/`@publish` before the service is running raises a clear `RuntimeError`.
183
+
184
+ ### 3. Publishing & Subscribing (Event-Driven)
185
+ React to real-time events efficiently.
186
+
187
+ ```python
188
+ from istos import Istos
189
+
190
+ istos = Istos()
191
+
192
+ # --- Subscriber ---
193
+ @istos.subscribe("drone/telemetry")
194
+ def on_telemetry(data):
195
+ # Triggered automatically when data is pushed to "drone/telemetry"
196
+ print(f"Received telemetry via network: {data}")
197
+
198
+ # --- Publisher ---
199
+ @istos.publish("drone/telemetry")
200
+ def get_telemetry():
201
+ # The return value is automatically published to the network!
202
+ return {"battery": 85, "altitude": 120}
203
+
204
+ if __name__ == "__main__":
205
+ # Call the wrapped publisher function to publish the result
206
+ get_telemetry()
207
+
208
+ # Or publish arbitrary data independently
209
+ # await istos.publish_once("drone/telemetry", {"battery": 80})
210
+
211
+ istos.run()
212
+ ```
213
+
214
+ **Brokerless durability.** Add `durable=True` and a subscriber that joins *late*
215
+ still receives everything — replayed peer-to-peer from the producer's cache, with
216
+ no Kafka/NATS broker to run:
217
+
218
+ ```python
219
+ @istos.publish("orders/created", durable=True, cache=1000) # producer keeps a replay log
220
+ async def created(order): return order
221
+
222
+ @istos.subscribe("orders/created", durable=True, replay=1000) # replays history + recovers
223
+ async def on_created(event): ...
224
+ ```
225
+
226
+ The producer *is* the log (Zenoh advanced pub/sub); pair it with the idempotency
227
+ ledger for effectively-once processing. See [Brokerless Durable Messaging](docs/user-guide/durable-messaging.md).
228
+
229
+ ### 4. Liveliness Tracking (Heartbeats)
230
+ Detect when nodes connect or drop off the network without polling.
231
+
232
+ ```python
233
+ # Announce that this node is alive on the network
234
+ istos.declare_liveliness("robot/camera1")
235
+
236
+ # Listen to the network for connection state changes
237
+ @istos.on_liveliness("robot/**")
238
+ def status_changed(key_expr: str, is_alive: bool):
239
+ if is_alive:
240
+ print(f"Node connected: {key_expr}")
241
+ else:
242
+ print(f"ALERT: Node crashed/disconnected -> {key_expr}")
243
+ ```
244
+
245
+ ### 5. One-Shot Commands & State Clearing
246
+ Use raw async functions when you want to act imperatively rather than relying on events.
247
+
248
+ ```python
249
+ # Quickly shoot out a piece of data
250
+ await istos.publish_once("fast/data/pulse", {"system": "ok"})
251
+
252
+ # Clear/erase network states, especially useful if using persistent StoragePlugins
253
+ await istos.delete_once("robot/cache/old_logs")
254
+ ```
255
+
256
+ ### 6. High-Performance Shared Memory (Zero-Copy)
257
+ When sending large data arrays (like HD video frames) between handlers on the same hardware, enable POSIX shared memory allocations to avoid copies.
258
+
259
+ ```python
260
+ @istos.publish("video/feed", use_shm=True)
261
+ def send_frame():
262
+ return large_data_array
263
+
264
+ # The framework automatically manages Zenoh ShmProviders natively!
265
+ ```
266
+
267
+ ### 7. Dependency Injection & Pluggability
268
+ Set global defaults on startup, or override them per-endpoint for polyglot persistence and sagas:
269
+
270
+ ```python
271
+ from istos import Istos
272
+ from istos.consistency import InMemoryStoragePlugin, SqlAlchemyStoragePlugin
273
+
274
+ istos = Istos(
275
+ storage=InMemoryStoragePlugin(), # Global default
276
+ )
277
+ ```
278
+
279
+ Each decorator can use its own serializer or storage plugin:
280
+
281
+ ```python
282
+ from istos.messages.serialization import MsgPackSerializer
283
+
284
+ # JSON and Memory by default
285
+ @istos.handle("robot/move")
286
+ async def move(distance: int): ...
287
+
288
+ # MsgPack and a durable SQL ledger specifically for this endpoint
289
+ @istos.handle("sensor/data", serializer=MsgPackSerializer(),
290
+ storage=SqlAlchemyStoragePlugin("postgresql+asyncpg://user:pass@db/istos"))
291
+ async def sensor(data): ...
292
+ ```
293
+
294
+ **Dependency injection with `Depends`.** `@handle`, `@subscribe`, `@publish`, `@query`, and `@on_liveliness` can all declare dependencies that Istos resolves per invocation — plain callables, async callables, sub-dependencies, and `yield` dependencies with setup/teardown. Use it as a default value or inside `Annotated`:
295
+
296
+ ```python
297
+ from typing import Annotated
298
+ from istos import Istos, Depends
299
+
300
+ istos = Istos()
301
+
302
+ async def get_db():
303
+ conn = await open_conn()
304
+ try:
305
+ yield conn # torn down after the handler replies
306
+ finally:
307
+ await conn.close()
308
+
309
+ @istos.handle("orders/create")
310
+ async def create(order_id: int, db: Annotated[Conn, Depends(get_db)]):
311
+ return await db.insert(order_id)
312
+ ```
313
+
314
+ Dependencies are cached per invocation (`use_cache=True` by default), sync dependencies are offloaded to a thread so they don't block the event loop, circular dependencies raise `DependencyCycleError`, and tests can swap any dependency via `istos.dependency_overrides[get_db] = fake_db`.
315
+
316
+ > **Streaming note:** on `@subscribe`/`@publish`, dependencies resolve **per message** — so a `yield` dependency opens/closes its resource on every message. For an expensive, long-lived resource (DB pool, sensor socket), create it once in the `lifespan` and inject the shared instance with a cheap `Depends` that just returns it; reserve per-message `yield` deps for genuinely per-message setup.
317
+
318
+ ### 8. Retry Policies
319
+ Add automatic retries with exponential backoff to any query or subscriber. Pass a simple integer or a full `RetryPolicy` for fine-grained control.
320
+
321
+ ```python
322
+ from istos.core.retry import RetryPolicy
323
+
324
+ # Simple — retry up to 5 times with default exponential backoff
325
+ @istos.query("weather/forecast", retry=5)
326
+ def get_forecast(result):
327
+ return result
328
+
329
+ # Subscriber with retries — if processing crashes, it retries before giving up
330
+ @istos.subscribe("sensor/readings", retry=3)
331
+ def on_reading(data):
332
+ save_to_database(data) # retried automatically on transient failures
333
+
334
+ # Advanced — full control over backoff timing and failure handling
335
+ @istos.query("weather/forecast", retry=RetryPolicy(
336
+ max_retries=10,
337
+ delay=1.0,
338
+ backoff_factor=3.0,
339
+ on_failure=lambda e: print(f"Dead letter: {e}")
340
+ ))
341
+ def get_forecast(result):
342
+ return result
343
+ ```
344
+
345
+ ### 9. Schema Validation
346
+ Istos automatically validates and type-coerces incoming parameters at the network boundary — before your business logic runs. Supports three modes:
347
+
348
+ ```python
349
+ from pydantic import BaseModel
350
+
351
+ # Mode 1: Type hints → auto-coercion
352
+ # Zenoh sends distance="15" (string), Istos casts it to int(15)
353
+ # Zenoh sends distance="hello" → rejected with a validation error reply
354
+ @istos.handle("robot/move")
355
+ async def move(distance: int, speed: str = "normal"):
356
+ return {"moved": distance, "speed": speed}
357
+
358
+ # Mode 2: Pydantic BaseModel → full schema validation
359
+ class MoveRequest(BaseModel):
360
+ distance: int
361
+ speed: str = "normal"
362
+
363
+ @istos.handle("robot/move")
364
+ async def move(request: MoveRequest):
365
+ # request is a fully validated Pydantic object with defaults applied
366
+ return {"moved": request.distance}
367
+
368
+ # Mode 3: No type hints → passthrough (backward compatible)
369
+ @istos.handle("robot/echo")
370
+ async def echo(message):
371
+ return {"echo": message}
372
+ ```
373
+
374
+ ### 10. Security: Transport, Authentication & Authorization
375
+
376
+ > [!WARNING]
377
+ > **Istos is unauthenticated by default.** With no configuration, a session runs
378
+ > in Zenoh **peer mode** with multicast scouting and no TLS — any peer on the
379
+ > local network can discover your node, invoke every `@handle`, and read every
380
+ > published value. Istos raises an `IstosSecurityWarning` (via `warnings.warn`,
381
+ > like urllib3's `InsecureRequestWarning`) whenever it opens such a session. You
382
+ > can escalate it to a hard failure in CI:
383
+ >
384
+ > ```python
385
+ > import warnings
386
+ > from istos import IstosSecurityWarning
387
+ > warnings.simplefilter("error", IstosSecurityWarning) # insecure config -> exception
388
+ > ```
389
+ >
390
+ > Before deploying, do **both** of the following:
391
+ > 1. **Secure the transport** — authenticate and encrypt the fabric (below).
392
+ > 2. **Authorize handlers** — gate who may invoke them (further below).
393
+
394
+ #### Transport Security & Authentication
395
+ Secure the system without hard-coding secrets. `IstosZenohConfig` loads properties from your environment (`.env` file or environment variables) using `pydantic-settings`.
396
+
397
+ ```env
398
+ # .env file
399
+ ISTOS_ZENOH_MODE=client
400
+ # Endpoints accept a JSON array or a comma-separated list:
401
+ ISTOS_ZENOH_CONNECT_ENDPOINTS=["tls/zenoh-router.local:7447"]
402
+ # ISTOS_ZENOH_CONNECT_ENDPOINTS=tls/router-a:7447,tls/router-b:7447
403
+
404
+ # Basic Auth
405
+ ISTOS_ZENOH_USERNAME=robot_1
406
+ ISTOS_ZENOH_PASSWORD=super_secret
407
+
408
+ # TLS / mTLS
409
+ ISTOS_ZENOH_ROOT_CA_CERTIFICATE=/path/to/ca.pem
410
+ # ISTOS_ZENOH_LISTEN_CERTIFICATE=/path/to/cert.pem
411
+ # ISTOS_ZENOH_LISTEN_PRIVATE_KEY=/path/to/key.pem
412
+ # ISTOS_ZENOH_ENABLE_MTLS=true
413
+
414
+ # Lock down discovery: disable UDP multicast scouting and rely on explicit endpoints
415
+ # ISTOS_ZENOH_MULTICAST_SCOUTING=false
416
+ ```
417
+
418
+ `IstosZenohConfig` is a Pydantic `BaseSettings` model, so it validates **at construction** (via field/model validators) and fails fast on structurally broken security config (malformed endpoints, a TLS cert without its key, mTLS without a CA, an invalid `mode`), warns when credentials are configured without TLS, and rejects unknown `ISTOS_ZENOH_*` variables so a typo can't silently disable auth. Secrets (`password`, `listen_private_key`) are `SecretStr`, so they don't leak into logs or reprs. For knobs the builder doesn't model, deep-merge raw Zenoh config via `additional_config={...}`. Session managers also accept `open_retries`/`open_retry_delay_s` to wait out a router that isn't up yet at startup.
419
+
420
+ Then pass it straight to Istos — it builds the session for you:
421
+
422
+ ```python
423
+ from istos import Istos
424
+ from istos.communication.sessions import IstosZenohConfig
425
+
426
+ # Auto-populates from .env!
427
+ config = IstosZenohConfig()
428
+
429
+ istos = Istos(config=config)
430
+ ```
431
+
432
+ `config=` accepts an `IstosZenohConfig` (built via `.build()` at construction) or a raw `zenoh.Config`. It's mutually exclusive with `session_manager=` — pass one or the other. If you need a sync session or full control, wire it yourself instead:
433
+
434
+ ```python
435
+ from istos.communication.sessions import AsyncZenohSession
436
+ istos = Istos(session_manager=AsyncZenohSession(config.build()))
437
+ ```
438
+
439
+ #### Advanced Security: Vault & Secret Managers (Programmatic Raw PEM)
440
+ For zero-trust environments, Zenoh accepts **raw multiline PEM strings** natively, so you don't need to write files to disk. You can bypass `.env` and pull secrets into `IstosZenohConfig` at startup:
441
+
442
+ ```python
443
+ # 1. Fetch from your secrets manager (HashiCorp Vault, AWS, LDAP, etc.)
444
+ secrets = vault.get_secret("istos/prod")
445
+
446
+ # 2. Inject raw strings directly into the config builder
447
+ config = IstosZenohConfig(
448
+ mode="client",
449
+ connect_endpoints=["tls/zenoh-router.local:7447"],
450
+ username=secrets["zenoh_user"],
451
+ password=secrets["zenoh_pass"],
452
+ root_ca_certificate=secrets["raw_ca_pem_string"], # raw multiline PEM
453
+ )
454
+
455
+ istos = Istos(session_manager=AsyncZenohSession(config.build()))
456
+ ```
457
+
458
+ #### Authorization
459
+ Securing the transport controls *who joins the fabric*. **Authorization** controls *what a joined peer may invoke*. Istos gives every handler an authorization hook. An **authorizer** receives an `AuthContext` (the key expression, parameters, and any attachment/token the caller sent) and returns `True` to allow the request, or `False`/raises `UnauthorizedError` to deny it. Sync and async authorizers are both supported. Denied requests never reach your handler and are answered with an `unauthorized` error.
460
+
461
+ ```python
462
+ from istos import Istos, TokenAuthorizer, AuthContext, UnauthorizedError
463
+
464
+ # App-wide: every handler (including built-in .istos/* endpoints) requires a token
465
+ istos = Istos(authorizer=TokenAuthorizer("super-secret-token"))
466
+
467
+ # Per-handler override — a custom rule for one endpoint
468
+ def admins_only(ctx: AuthContext) -> bool:
469
+ return ctx.token in {"alice-key", "bob-key"}
470
+
471
+ @istos.handle("fleet/shutdown", authorizer=admins_only)
472
+ async def shutdown():
473
+ return {"stopping": True}
474
+ ```
475
+
476
+ Callers attach their token via `attachment=`:
477
+
478
+ ```python
479
+ await istos.query_once("fleet/shutdown", attachment="alice-key")
480
+ ```
481
+
482
+ > **Built-in endpoints** (`.istos/health`, `.istos/ready`, `.istos/metrics`) and
483
+ > `serve_docs()` inherit the app-wide authorizer. If you leave the app
484
+ > unauthenticated, Istos warns that these endpoints — including the AsyncAPI
485
+ > document that describes your entire API surface — are network-reachable by any
486
+ > peer. Set an `authorizer` (or pass one to `serve_docs(authorizer=...)`) to
487
+ > protect them.
488
+
489
+ #### A note on serialization
490
+ Istos ships `JsonSerializer` (default), `RawSerializer` (bytes/str passthrough for pre-encoded or binary payloads), `MsgPackSerializer`, `PydanticSerializer`, `ProtobufSerializer`, `YamlSerializer`, and a `Base64Serializer` wrapper. `JsonSerializer` tolerates common types like `datetime`, `Decimal`, and `UUID` (stringified), and `MsgPackSerializer` pins string decoding for cross-version consistency.
491
+
492
+ It does **not** ship a pickle-based serializer: `pickle.loads` executes arbitrary code embedded in its input, which on a fabric where any peer can publish to a key is remote code execution by design.
493
+
494
+ ## Testing
495
+
496
+ Istos includes `IstosTestClient` for in-process testing without a Zenoh network:
497
+
498
+ ```python
499
+ from istos import Istos
500
+ from istos.testing import IstosTestClient
501
+
502
+ istos = Istos()
503
+
504
+ @istos.handle("robot/move")
505
+ async def move(distance: int):
506
+ return {"moved": distance}
507
+
508
+ client = IstosTestClient(istos)
509
+ result = await client.query("robot/move", distance=10)
510
+ ```
511
+
512
+ Run the full test suite:
513
+
514
+ ```bash
515
+ uv pip install -e ".[dev]"
516
+ pytest tests/ -m "not integration" # unit tests
517
+ pytest tests/ # includes network integration tests
518
+ ```
519
+
520
+ ## Production Features
521
+
522
+ - **Logging** — text or JSON under `istos.*`. We don't reconfigure your root logger unless you ask (`configure_logging=True` / `configure_logging()`).
523
+ - **Health** — `.istos/health` / `.istos/ready` (and `/livez` / `/readyz` with `http_port`)
524
+ - **Metrics** — `.istos/metrics` (and `/metrics`)
525
+ - **Capabilities** — `.istos/capabilities` lists handlers/streams with schemas
526
+ - **HTTP / SSE** — `http_port` + `http=` on handle/stream
527
+ - **Tracing** — optional OTel (`istos[otel]`)
528
+ - **Middleware** — correlation IDs, logging, your own
529
+ - **Exceptions** — `@exception_handler`
530
+ - **Shutdown** — SIGINT/SIGTERM
531
+ - **Storage** — Redis, SQLAlchemy, S3 persist, JWT extras
532
+
533
+ [Deployment](docs/user-guide/deployment.md) · [HTTP Gateway](docs/user-guide/http-gateway.md)
534
+
535
+ ## CLI
536
+
537
+ ```bash
538
+ istos new my-service # Scaffold a new project
539
+ istos docs # Serve documentation locally
540
+ istos version # Print installed version
541
+ ```
542
+
543
+ ## Contributing
544
+ Contributions and pull requests are welcome! Ensure tests pass and type hints are satisfied.
545
+
546
+ 1. Fork the repository
547
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
548
+ 3. Commit your changes
549
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
550
+ 5. Open a Pull Request
551
+
552
+ ---
553
+ **License**: Apache-2.0
554
+
555
+ **Python**: 3.10, 3.11, 3.12, 3.13, and 3.14