Skip to content

Commit 3955ffa

Browse files
zeevdrclaude
andauthored
docs: fix watcher field-registration pattern across all docs (#102)
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 <noreply@anthropic.com>
1 parent b8b59d2 commit 3955ffa

3 files changed

Lines changed: 29 additions & 32 deletions

File tree

sdk/docs/async.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ Same constructor options as `ConfigClient` — see [Configuration](configuration
3030
from opendecree import AsyncConfigClient
3131

3232
async with AsyncConfigClient("localhost:9090", subject="myapp") as client:
33-
async with client.watch("tenant-id") as watcher:
34-
fee = watcher.field("payments.fee", float, default=0.01)
35-
enabled = watcher.field("payments.enabled", bool, default=False)
33+
watcher = client.watch("tenant-id")
34+
fee = watcher.field("payments.fee", float, default=0.01)
35+
enabled = watcher.field("payments.enabled", bool, default=False)
3636

37+
async with watcher:
3738
# .value works the same
3839
print(fee.value)
3940

@@ -47,9 +48,10 @@ async with AsyncConfigClient("localhost:9090", subject="myapp") as client:
4748
Use `async for` instead of `for`:
4849

4950
```python
50-
async with client.watch("tenant-id") as watcher:
51-
fee = watcher.field("payments.fee", float, default=0.01)
51+
watcher = client.watch("tenant-id")
52+
fee = watcher.field("payments.fee", float, default=0.01)
5253

54+
async with watcher:
5355
async for change in fee.changes():
5456
print(f"{change.old_value} -> {change.new_value}")
5557
```

sdk/docs/quickstart.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,15 @@ with ConfigClient("localhost:9090", subject="myapp") as client:
6161

6262
```python
6363
with ConfigClient("localhost:9090", subject="myapp") as client:
64-
with client.watch("tenant-id") as watcher:
65-
fee = watcher.field("payments.fee", float, default=0.01)
64+
watcher = client.watch("tenant-id")
65+
fee = watcher.field("payments.fee", float, default=0.01)
6666

67-
print(fee.value) # current value, always fresh
67+
@fee.on_change
68+
def on_fee_change(old: float, new: float):
69+
print(f"Fee changed: {old} -> {new}")
6870

69-
@fee.on_change
70-
def on_fee_change(old: float, new: float):
71-
print(f"Fee changed: {old} -> {new}")
71+
with watcher:
72+
print(fee.value) # current value, always fresh
7273
```
7374

7475
See [Watching](watching.md) for more patterns.

sdk/docs/watching.md

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@ Live config subscriptions via `ConfigWatcher` and `WatchedField[T]`.
44

55
## Basic usage
66

7-
Create a watcher from a client. Register fields before entering the context manager:
7+
Create a watcher from a client. Register fields before starting the watcher:
88

99
```python
1010
from opendecree import ConfigClient
1111

1212
with ConfigClient("localhost:9090", subject="myapp") as client:
13-
with client.watch("tenant-id") as watcher:
14-
fee = watcher.field("payments.fee", float, default=0.01)
15-
enabled = watcher.field("payments.enabled", bool, default=False)
13+
watcher = client.watch("tenant-id")
14+
fee = watcher.field("payments.fee", float, default=0.01)
15+
enabled = watcher.field("payments.enabled", bool, default=False)
1616

17+
with watcher:
1718
# Read current values
1819
print(fee.value) # 0.025 (float, always fresh)
1920
print(enabled.value) # True (bool)
@@ -79,29 +80,19 @@ The iterator blocks until a change arrives. It stops when the watcher exits.
7980

8081
## Lifecycle
8182

82-
Register fields **before** the `with` block. Fields cannot be added after the watcher starts:
83+
Register fields **before** starting the watcher. Calling `field()` after `start()` raises `RuntimeError`:
8384

8485
```python
8586
watcher = client.watch("tenant-id")
8687

8788
# Register fields first
8889
fee = watcher.field("payments.fee", float, default=0.01)
8990

90-
# Then start
91+
# Then start — loads snapshot and subscribes
9192
with watcher:
9293
print(fee.value)
9394
```
9495

95-
Or equivalently:
96-
97-
```python
98-
with client.watch("tenant-id") as watcher:
99-
fee = watcher.field("payments.fee", float, default=0.01)
100-
# ...
101-
```
102-
103-
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.
104-
10596
## Auto-reconnect
10697

10798
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:
119110

120111
```python
121112
with ConfigClient("localhost:9090", subject="myapp") as client:
122-
with client.watch("tenant-a") as watcher_a:
123-
with client.watch("tenant-b") as watcher_b:
124-
fee_a = watcher_a.field("payments.fee", float, default=0.01)
125-
fee_b = watcher_b.field("payments.fee", float, default=0.01)
126-
# Both update independently
113+
watcher_a = client.watch("tenant-a")
114+
watcher_b = client.watch("tenant-b")
115+
fee_a = watcher_a.field("payments.fee", float, default=0.01)
116+
fee_b = watcher_b.field("payments.fee", float, default=0.01)
117+
118+
with watcher_a, watcher_b:
119+
# Both update independently
120+
print(fee_a.value, fee_b.value)
127121
```
128122

129123
## Next steps

0 commit comments

Comments
 (0)