From 652d7d5cd137fa379992db2b922c344df9c3738a Mon Sep 17 00:00:00 2001 From: zeevdr Date: Mon, 25 May 2026 12:48:32 +0300 Subject: [PATCH] docs: fix watcher field-registration pattern across all docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All three docs (quickstart, watching, async) showed watcher.field() called inside the context manager block, which raises RuntimeError at runtime. The implementation enforces field registration before start() / __enter__. Unified on the canonical two-step pattern: create watcher, register fields, then enter the context manager. Also removed the contradictory "Or equivalently" block and confusing "Wait —" note from watching.md Lifecycle section, and fixed the Multiple watchers example which had the same error. Closes #65 Co-Authored-By: Claude --- sdk/docs/async.md | 12 +++++++----- sdk/docs/quickstart.md | 13 +++++++------ sdk/docs/watching.md | 36 +++++++++++++++--------------------- 3 files changed, 29 insertions(+), 32 deletions(-) diff --git a/sdk/docs/async.md b/sdk/docs/async.md index b4a715b..8c061ca 100644 --- a/sdk/docs/async.md +++ b/sdk/docs/async.md @@ -30,10 +30,11 @@ Same constructor options as `ConfigClient` — see [Configuration](configuration from opendecree import AsyncConfigClient async with AsyncConfigClient("localhost:9090", subject="myapp") as client: - async with client.watch("tenant-id") as watcher: - fee = watcher.field("payments.fee", float, default=0.01) - enabled = watcher.field("payments.enabled", bool, default=False) + watcher = client.watch("tenant-id") + fee = watcher.field("payments.fee", float, default=0.01) + enabled = watcher.field("payments.enabled", bool, default=False) + async with watcher: # .value works the same print(fee.value) @@ -47,9 +48,10 @@ async with AsyncConfigClient("localhost:9090", subject="myapp") as client: Use `async for` instead of `for`: ```python -async with client.watch("tenant-id") as watcher: - fee = watcher.field("payments.fee", float, default=0.01) +watcher = client.watch("tenant-id") +fee = watcher.field("payments.fee", float, default=0.01) +async with watcher: async for change in fee.changes(): print(f"{change.old_value} -> {change.new_value}") ``` diff --git a/sdk/docs/quickstart.md b/sdk/docs/quickstart.md index fa7d50c..1a3e1bd 100644 --- a/sdk/docs/quickstart.md +++ b/sdk/docs/quickstart.md @@ -61,14 +61,15 @@ with ConfigClient("localhost:9090", subject="myapp") as client: ```python with ConfigClient("localhost:9090", subject="myapp") as client: - with client.watch("tenant-id") as watcher: - fee = watcher.field("payments.fee", float, default=0.01) + watcher = client.watch("tenant-id") + fee = watcher.field("payments.fee", float, default=0.01) - print(fee.value) # current value, always fresh + @fee.on_change + def on_fee_change(old: float, new: float): + print(f"Fee changed: {old} -> {new}") - @fee.on_change - def on_fee_change(old: float, new: float): - print(f"Fee changed: {old} -> {new}") + with watcher: + print(fee.value) # current value, always fresh ``` See [Watching](watching.md) for more patterns. diff --git a/sdk/docs/watching.md b/sdk/docs/watching.md index 494a51e..d735c2e 100644 --- a/sdk/docs/watching.md +++ b/sdk/docs/watching.md @@ -4,16 +4,17 @@ Live config subscriptions via `ConfigWatcher` and `WatchedField[T]`. ## Basic usage -Create a watcher from a client. Register fields before entering the context manager: +Create a watcher from a client. Register fields before starting the watcher: ```python from opendecree import ConfigClient with ConfigClient("localhost:9090", subject="myapp") as client: - with client.watch("tenant-id") as watcher: - fee = watcher.field("payments.fee", float, default=0.01) - enabled = watcher.field("payments.enabled", bool, default=False) + watcher = client.watch("tenant-id") + fee = watcher.field("payments.fee", float, default=0.01) + enabled = watcher.field("payments.enabled", bool, default=False) + with watcher: # Read current values print(fee.value) # 0.025 (float, always fresh) print(enabled.value) # True (bool) @@ -79,7 +80,7 @@ The iterator blocks until a change arrives. It stops when the watcher exits. ## Lifecycle -Register fields **before** the `with` block. Fields cannot be added after the watcher starts: +Register fields **before** starting the watcher. Calling `field()` after `start()` raises `RuntimeError`: ```python watcher = client.watch("tenant-id") @@ -87,21 +88,11 @@ watcher = client.watch("tenant-id") # Register fields first fee = watcher.field("payments.fee", float, default=0.01) -# Then start +# Then start — loads snapshot and subscribes with watcher: print(fee.value) ``` -Or equivalently: - -```python -with client.watch("tenant-id") as watcher: - fee = watcher.field("payments.fee", float, default=0.01) - # ... -``` - -Wait — fields must be registered **before** `start()`. When using the two-line form, register between `watch()` and `with`. When using the one-line form, the watcher loads a snapshot on enter, so fields registered inside the `with` block will get their initial values from the snapshot. - ## Auto-reconnect If the gRPC stream drops (server restart, network issue), the watcher automatically reconnects with exponential backoff: @@ -119,11 +110,14 @@ You can create multiple watchers for different tenants: ```python with ConfigClient("localhost:9090", subject="myapp") as client: - with client.watch("tenant-a") as watcher_a: - with client.watch("tenant-b") as watcher_b: - fee_a = watcher_a.field("payments.fee", float, default=0.01) - fee_b = watcher_b.field("payments.fee", float, default=0.01) - # Both update independently + watcher_a = client.watch("tenant-a") + watcher_b = client.watch("tenant-b") + fee_a = watcher_a.field("payments.fee", float, default=0.01) + fee_b = watcher_b.field("payments.fee", float, default=0.01) + + with watcher_a, watcher_b: + # Both update independently + print(fee_a.value, fee_b.value) ``` ## Next steps