Skip to content

Commit c6c313a

Browse files
author
nsz
committed
config management
1 parent 445f204 commit c6c313a

14 files changed

Lines changed: 710 additions & 51 deletions

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,30 @@ tw = TaskWarrior(
8888
)
8989
```
9090

91+
### Live configuration updates
92+
93+
`config_store` is the live interface to the taskrc file. Changes made via
94+
`set_value()` or `set_sync_config()` are immediately effective on the next
95+
adapter call — no restart or adapter recreation required.
96+
97+
```python
98+
tw = TaskWarrior()
99+
100+
# Configure remote sync at runtime
101+
tw.config_store.set_value("sync.server.origin", "https://sync.example.com")
102+
tw.config_store.set_value("sync.encryption.secret", "my-passphrase")
103+
tw.synchronize() # uses the new config immediately
104+
105+
# Or replace the whole sync block at once
106+
tw.config_store.set_sync_config({
107+
"sync.local.server_dir": "/mnt/shared/taskserver",
108+
})
109+
tw.synchronize()
110+
```
111+
112+
> **Note:** Changing `data_location` at runtime is not supported. Create a
113+
> new `TaskWarrior` instance if you need a different data directory.
114+
91115
## Architecture
92116

93117
```

docs/runtime-config.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# Runtime Configuration Guide
2+
3+
This guide shows how to modify TaskWarrior configuration at runtime in your
4+
application — without restarting or recreating the `TaskWarrior` instance.
5+
6+
---
7+
8+
## What changed
9+
10+
Prior to this feature, sync parameters and other taskrc settings were read
11+
**once** at construction time. Any change required recreating the adapter.
12+
13+
Now both adapters read configuration **lazily** from `config_store` on every
14+
call. A single `set_value()` is enough — no restart needed.
15+
16+
---
17+
18+
## Step 1 — Nothing changes at construction
19+
20+
Your existing initialization code stays exactly the same:
21+
22+
```python
23+
# Before — still valid, nothing to change
24+
self._tw = TaskWarrior(taskrc_file="/path/to/.taskrc")
25+
26+
# Or with explicit data directory
27+
self._tw = TaskWarrior(
28+
taskrc_file="/path/to/.taskrc",
29+
data_location="/path/to/task/data",
30+
)
31+
```
32+
33+
---
34+
35+
## Step 2 — Modify config at runtime via `config_store`
36+
37+
`TaskWarrior` exposes a `config_store` attribute. Use it to read or write any
38+
taskrc key at any point after construction.
39+
40+
### Set a single key
41+
42+
```python
43+
self._tw.config_store.set_value("sync.server.origin", "https://sync.example.com")
44+
```
45+
46+
The change is written to disk **and** the in-memory cache is refreshed
47+
immediately. The next adapter call picks it up automatically.
48+
49+
### Delete a key
50+
51+
```python
52+
self._tw.config_store.delete_value("sync.encryption.secret")
53+
```
54+
55+
### Read the current value
56+
57+
```python
58+
origin = self._tw.config_store.config.get("sync.server.origin")
59+
```
60+
61+
---
62+
63+
## Step 3 — Replace sync configuration
64+
65+
Use `set_sync_config()` to **replace** all `sync.*` keys in one atomic
66+
operation (existing keys not in the new dict are removed):
67+
68+
```python
69+
# Switch to remote sync
70+
self._tw.config_store.set_sync_config({
71+
"sync.server.origin": "https://taskchampion.example.com",
72+
"sync.server.client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
73+
"sync.encryption.secret": "my-passphrase",
74+
})
75+
76+
# Switch to local sync (previous remote keys are removed automatically)
77+
self._tw.config_store.set_sync_config({
78+
"sync.local.server_dir": "/mnt/shared/taskserver",
79+
})
80+
81+
# Disable sync entirely
82+
self._tw.config_store.set_sync_config({})
83+
```
84+
85+
After any of the above, just call:
86+
87+
```python
88+
self._tw.synchronize() # uses the new config immediately
89+
```
90+
91+
---
92+
93+
## Step 4 — Real-world patterns
94+
95+
### Pattern A — Configure sync from user input
96+
97+
```python
98+
class MyApp:
99+
def __init__(self, taskrc: str):
100+
self._tw = TaskWarrior(taskrc_file=taskrc)
101+
102+
def configure_sync(self, server_url: str, secret: str) -> None:
103+
self._tw.config_store.set_sync_config({
104+
"sync.server.origin": server_url,
105+
"sync.encryption.secret": secret,
106+
})
107+
108+
def sync(self) -> None:
109+
if self._tw.is_sync_configured():
110+
self._tw.synchronize()
111+
```
112+
113+
### Pattern B — Toggle sync on / off
114+
115+
```python
116+
def enable_sync(self, server_dir: str) -> None:
117+
self._tw.config_store.set_value("sync.local.server_dir", server_dir)
118+
119+
def disable_sync(self) -> None:
120+
self._tw.config_store.set_sync_config({}) # removes all sync.* keys
121+
```
122+
123+
### Pattern C — Read then modify
124+
125+
```python
126+
def rotate_secret(self, new_secret: str) -> None:
127+
cfg = self._tw.config_store.get_sync_config()
128+
if not cfg.get("sync.server.origin"):
129+
raise RuntimeError("Remote sync is not configured")
130+
self._tw.config_store.set_value("sync.encryption.secret", new_secret)
131+
```
132+
133+
---
134+
135+
## What you must NOT change at runtime
136+
137+
| Setting | Why |
138+
|---------|-----|
139+
| `rc.data.location` | The SQLite database (`Replica`) is opened once at construction; changing the path has no effect on the running adapter. |
140+
141+
If you need a different data directory, create a new `TaskWarrior` instance:
142+
143+
```python
144+
# Correct way to switch data directory
145+
self._tw = TaskWarrior(
146+
taskrc_file=self._taskrc,
147+
data_location="/new/path",
148+
)
149+
```
150+
151+
---
152+
153+
## Quick reference
154+
155+
| Goal | Method |
156+
|------|--------|
157+
| Write a single key | `tw.config_store.set_value(key, value)` |
158+
| Delete a single key | `tw.config_store.delete_value(key)` |
159+
| Replace all `sync.*` keys | `tw.config_store.set_sync_config({…})` |
160+
| Read a value | `tw.config_store.config.get(key)` |
161+
| Read all sync keys | `tw.config_store.get_sync_config()` |
162+
| Reload from disk | `tw.config_store.refresh()` |
163+
| Check sync active | `tw.is_sync_configured()` |

docs/sync.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ pytaskwarrior supports two synchronization modes via the default
1010

1111
Both modes expose the same Python API: `tw.synchronize()` and `tw.is_sync_configured()`.
1212

13+
!!! tip "Runtime configuration"
14+
Sync settings can be modified at any time without recreating the adapter.
15+
See the [Runtime Configuration guide](runtime-config.md) for step-by-step instructions.
16+
1317
---
1418

1519
## Prerequisites

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ nav:
6262
- Contexts: examples/contexts.md
6363
- Advanced: examples/advanced.md
6464
- Tips & Tricks: tips-and-tricks.md
65+
- Runtime Configuration: runtime-config.md
6566
- Synchronization: sync.md
6667
- LLMs Guides:
6768
- Overview: llms/llms-taskwarrior.md

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "pytaskwarrior"
3-
version = "3.0.0a4"
3+
version = "3.0.0a5"
44
description = "Taskwarrior wrapper python module"
55
readme = "PYPI_README.md"
66
requires-python = ">=3.12"

0 commit comments

Comments
 (0)