0
# Process Management
1
2
Automatic process restarting and management functionality that watches for file changes and restarts processes accordingly. Supports both Python functions and shell commands with configurable signal handling, graceful shutdown, and callback mechanisms.
3
4
## Capabilities
5
6
### Synchronous Process Management
7
8
Run a process and automatically restart it when file changes are detected. Supports Python functions and shell commands with extensive configuration options.
9
10
```python { .api }
11
def run_process(
12
*paths: Union[Path, str],
13
target: Union[str, Callable[..., Any]],
14
args: Tuple[Any, ...] = (),
15
kwargs: Optional[Dict[str, Any]] = None,
16
target_type: Literal['function', 'command', 'auto'] = 'auto',
17
callback: Optional[Callable[[Set[FileChange]], None]] = None,
18
watch_filter: Optional[Callable[[Change, str], bool]] = DefaultFilter(),
19
grace_period: float = 0,
20
debounce: int = 1_600,
21
step: int = 50,
22
debug: Optional[bool] = None,
23
sigint_timeout: int = 5,
24
sigkill_timeout: int = 1,
25
recursive: bool = True,
26
ignore_permission_denied: bool = False,
27
) -> int:
28
"""
29
Run a process and restart it upon file changes.
30
31
Parameters:
32
- *paths: Filesystem paths to watch (same as watch())
33
- target: Function or command to run
34
- args: Arguments to pass to target (function only)
35
- kwargs: Keyword arguments to pass to target (function only)
36
- target_type: 'function', 'command', or 'auto' (uses detect_target_type)
37
- callback: Function called on each reload with changes as argument
38
- watch_filter: File filter (same as watch())
39
- grace_period: Seconds after process start before watching changes
40
- debounce: Debounce time in ms (same as watch())
41
- step: Step time in ms (same as watch())
42
- debug: Enable debug output
43
- sigint_timeout: Seconds to wait after SIGINT before SIGKILL
44
- sigkill_timeout: Seconds to wait after SIGKILL before exception
45
- recursive: Watch recursively (same as watch())
46
- ignore_permission_denied: Ignore permission errors
47
48
Returns:
49
int: Number of times the process was reloaded
50
51
Note:
52
Uses multiprocessing.get_context('spawn').Process for Python functions
53
to avoid forking and improve code reload/import behavior.
54
"""
55
```
56
57
**Usage Examples:**
58
59
```python
60
from watchfiles import run_process
61
62
# Run a Python function
63
def my_app(name, port=8000):
64
print(f"Starting {name} on port {port}...")
65
# App logic here
66
67
# Watch current directory and restart function on changes
68
reloads = run_process('.', target=my_app, args=('MyApp',), kwargs={'port': 3000})
69
print(f"Process reloaded {reloads} times")
70
71
# Run a shell command
72
run_process('./src', target='python main.py')
73
74
# With callback function
75
def on_reload(changes):
76
print(f"Reloading due to: {changes}")
77
78
run_process('./src', target='python server.py', callback=on_reload)
79
80
# With grace period (wait before watching)
81
run_process('./src', target=my_app, grace_period=2.0)
82
```
83
84
### Asynchronous Process Management
85
86
Async equivalent of run_process with support for async callbacks and proper integration with async event loops.
87
88
```python { .api }
89
async def arun_process(
90
*paths: Union[Path, str],
91
target: Union[str, Callable[..., Any]],
92
args: Tuple[Any, ...] = (),
93
kwargs: Optional[Dict[str, Any]] = None,
94
target_type: Literal['function', 'command', 'auto'] = 'auto',
95
callback: Optional[Callable[[Set[FileChange]], Any]] = None,
96
watch_filter: Optional[Callable[[Change, str], bool]] = DefaultFilter(),
97
grace_period: float = 0,
98
debounce: int = 1_600,
99
step: int = 50,
100
debug: Optional[bool] = None,
101
recursive: bool = True,
102
ignore_permission_denied: bool = False,
103
) -> int:
104
"""
105
Async version of run_process.
106
107
Parameters:
108
Same as run_process except:
109
- callback: Can be a coroutine function
110
- No sigint_timeout/sigkill_timeout (handled by async framework)
111
112
Returns:
113
int: Number of times the process was reloaded
114
115
Note:
116
Starting/stopping processes and watching done in separate threads.
117
KeyboardInterrupt must be caught at asyncio.run() level.
118
"""
119
```
120
121
**Usage Examples:**
122
123
```python
124
import asyncio
125
from watchfiles import arun_process
126
127
async def async_callback(changes):
128
await asyncio.sleep(0.1) # Async operation
129
print(f"Async reload callback: {changes}")
130
131
def my_app():
132
print("App running...")
133
134
async def main():
135
reloads = await arun_process('./src', target=my_app, callback=async_callback)
136
print(f"Reloaded {reloads} times")
137
138
try:
139
asyncio.run(main())
140
except KeyboardInterrupt:
141
print("Stopped via KeyboardInterrupt")
142
```
143
144
### Target Type Detection
145
146
Automatically detect whether a target should be run as a function or command when `target_type='auto'`.
147
148
```python { .api }
149
def detect_target_type(target: Union[str, Callable[..., Any]]) -> Literal['function', 'command']:
150
"""
151
Used by run_process and arun_process to determine the target type when target_type is 'auto'.
152
153
Detects the target type - either 'function' or 'command'. This method is only called with target_type='auto'.
154
155
The following logic is employed:
156
- If target is not a string, it is assumed to be a function
157
- If target ends with '.py' or '.sh', it is assumed to be a command
158
- Otherwise, the target is assumed to be a function if it matches the regex [a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)+
159
160
If this logic does not work for you, specify the target type explicitly using the target_type function argument
161
or --target-type command line argument.
162
163
Parameters:
164
- target: The target value to analyze
165
166
Returns:
167
Either 'function' or 'command'
168
"""
169
```
170
171
**Usage Examples:**
172
173
```python
174
from watchfiles import detect_target_type
175
176
# These return 'function'
177
print(detect_target_type(my_function)) # 'function'
178
print(detect_target_type('mymodule.main')) # 'function'
179
180
# These return 'command'
181
print(detect_target_type('python main.py')) # 'command'
182
print(detect_target_type('main.py')) # 'command'
183
print(detect_target_type('./script.sh')) # 'command'
184
```
185
186
### Import String Utility
187
188
Import a callable from a dotted module path string, used internally for function targets.
189
190
```python { .api }
191
def import_string(dotted_path: str) -> Any:
192
"""
193
Import a dotted module path and return the attribute/class designated by the
194
last name in the path. Raise ImportError if the import fails.
195
196
Stolen approximately from django. This is used to import function targets
197
when they are specified as dotted strings.
198
199
Parameters:
200
- dotted_path: Dotted module path like 'mypackage.module.function'
201
202
Returns:
203
The imported attribute/function/class
204
205
Raises:
206
ImportError: If dotted_path doesn't look like a module path or if the
207
module doesn't define the specified attribute
208
"""
209
```
210
211
**Usage Examples:**
212
213
```python
214
from watchfiles import import_string
215
216
# Import a function
217
func = import_string('mypackage.utils.helper_function')
218
result = func(arg1, arg2)
219
220
# Import a class
221
MyClass = import_string('mypackage.models.MyModel')
222
instance = MyClass()
223
```
224
225
### Process Environment Variables
226
227
When processes are started, the `WATCHFILES_CHANGES` environment variable is set to a JSON string containing the file changes that triggered the restart:
228
229
```python
230
import os
231
import json
232
233
def my_target_function():
234
# Access changes that triggered this restart
235
changes_json = os.getenv('WATCHFILES_CHANGES', '[]')
236
changes = json.loads(changes_json)
237
print(f"Restarted due to: {changes}")
238
# changes is a list of [change_type_string, path] pairs
239
```
240
241
**Shell Command Example:**
242
243
```bash
244
#!/bin/bash
245
echo "Changes that triggered restart: $WATCHFILES_CHANGES"
246
```
247
248
### Signal Handling
249
250
Process management includes comprehensive signal handling for graceful shutdown:
251
252
1. **SIGINT** (Ctrl+C) sent to process first
253
2. Wait `sigint_timeout` seconds for graceful shutdown
254
3. If process still running, send **SIGKILL**
255
4. Wait `sigkill_timeout` seconds before raising exception
256
257
**SIGTERM Handling:**
258
The watchfiles process automatically registers a SIGTERM handler that raises KeyboardInterrupt, ensuring clean shutdown in containerized environments.
259
260
## Types
261
262
```python { .api }
263
# Internal types used by process management
264
class CombinedProcess:
265
"""Wrapper for both subprocess.Popen and multiprocessing.Process"""
266
267
def __init__(self, p: Union[SpawnProcess, subprocess.Popen[bytes]]) -> None: ...
268
def stop(self, sigint_timeout: int = 5, sigkill_timeout: int = 1) -> None: ...
269
def is_alive(self) -> bool: ...
270
@property
271
def pid(self) -> int: ...
272
def join(self, timeout: int) -> None: ...
273
@property
274
def exitcode(self) -> Optional[int]: ...
275
```