or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

tessl/pypi-pytest-watcher

Automatically rerun your tests on file modifications

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
pypipkg:pypi/pytest-watcher@0.4.x

To install, run

npx @tessl/cli install tessl/pypi-pytest-watcher@0.4.0

0

# pytest-watcher

1

2

A command-line tool that automatically reruns pytest tests whenever Python files change, providing fast feedback during development with native filesystem monitoring and interactive controls.

3

4

## Package Information

5

6

- **Package Name**: pytest-watcher

7

- **Package Type**: PyPI

8

- **Language**: Python

9

- **Installation**: `pip install pytest-watcher`

10

- **Python Compatibility**: >= 3.7.0

11

- **License**: MIT

12

13

## Core Imports

14

15

For programmatic usage:

16

17

```python

18

from pytest_watcher import run

19

```

20

21

## Basic Usage

22

23

### Command Line Interface

24

25

Basic usage with current directory:

26

27

```bash

28

ptw .

29

```

30

31

Watch specific directory:

32

33

```bash

34

ptw /path/to/project

35

```

36

37

Pass arguments to pytest:

38

39

```bash

40

ptw . -x --lf --nf

41

```

42

43

### Advanced Usage

44

45

Use different test runner:

46

47

```bash

48

ptw . --runner tox

49

```

50

51

Watch specific file patterns:

52

53

```bash

54

ptw . --patterns '*.py,pyproject.toml'

55

```

56

57

Ignore certain patterns:

58

59

```bash

60

ptw . --ignore-patterns 'settings.py,__pycache__/*'

61

```

62

63

Run immediately and clear screen:

64

65

```bash

66

ptw . --now --clear

67

```

68

69

Custom delay before running tests:

70

71

```bash

72

ptw . --delay 0.5

73

```

74

75

## Configuration

76

77

Configure via `pyproject.toml`:

78

79

```toml

80

[tool.pytest-watcher]

81

now = false

82

clear = true

83

delay = 0.2

84

runner = "pytest"

85

runner_args = ["--verbose", "--tb=short"]

86

patterns = ["*.py"]

87

ignore_patterns = ["*/migrations/*", "*/venv/*"]

88

```

89

90

## Capabilities

91

92

### Command Line Interface

93

94

The primary interface for pytest-watcher, providing file watching with automatic test execution.

95

96

```python { .api }

97

def run() -> None:

98

"""

99

Main entry point that starts the file watcher and interactive loop.

100

101

Parses command line arguments, sets up filesystem monitoring,

102

and runs the interactive terminal interface until quit.

103

104

Returns:

105

None (runs until manually terminated)

106

"""

107

108

def main_loop(trigger: Trigger, config: Config, term: Terminal) -> None:

109

"""

110

Execute one iteration of the main event loop.

111

112

Checks for triggered test runs, executes tests if needed,

113

captures keystrokes, and processes interactive commands.

114

115

Args:

116

trigger (Trigger): Test execution trigger

117

config (Config): Current configuration

118

term (Terminal): Terminal interface

119

"""

120

```

121

122

**CLI Arguments:**

123

124

```bash { .api }

125

ptw <path> [options] [-- pytest_args...]

126

127

Positional Arguments:

128

path Path to watch for file changes

129

130

Optional Arguments:

131

--now Trigger test run immediately on startup

132

--clear Clear terminal screen before each test run

133

--delay DELAY Delay in seconds before triggering test run (default: 0.2)

134

--runner RUNNER Executable for running tests (default: "pytest")

135

--patterns PATTERNS Comma-separated Unix-style file patterns to watch (default: "*.py")

136

--ignore-patterns PATTERNS Comma-separated Unix-style file patterns to ignore

137

--version Show version information

138

```

139

140

### Interactive Controls

141

142

During execution, pytest-watcher provides keyboard shortcuts for controlling test execution:

143

144

```text { .api }

145

Interactive Commands:

146

Enter : Invoke test runner manually

147

r : Reset all runner arguments

148

c : Change runner arguments interactively

149

f : Run only failed tests (add --lf flag)

150

p : Drop to pdb on failure (add --pdb flag)

151

v : Increase verbosity (add -v flag)

152

e : Erase terminal screen

153

w : Show full menu

154

q : Quit pytest-watcher

155

```

156

157

### Configuration System

158

159

Configuration management with file-based and CLI override support.

160

161

```python { .api }

162

class Config:

163

"""

164

Configuration data class for pytest-watcher settings.

165

166

Attributes:

167

path (Path): Directory path to watch for changes

168

now (bool): Whether to run tests immediately on startup

169

clear (bool): Whether to clear screen before test runs

170

delay (float): Seconds to delay before triggering tests

171

runner (str): Test runner executable name

172

runner_args (List[str]): Additional arguments for test runner

173

patterns (List[str]): File patterns to watch for changes

174

ignore_patterns (List[str]): File patterns to ignore

175

"""

176

177

@classmethod

178

def create(cls, namespace: Namespace, extra_args: Optional[List[str]] = None) -> "Config":

179

"""

180

Create Config instance from parsed arguments and configuration file.

181

182

Args:

183

namespace (Namespace): Parsed command line arguments

184

extra_args (Optional[List[str]]): Additional runner arguments

185

186

Returns:

187

Config: Configured instance with merged settings

188

"""

189

```

190

191

```python { .api }

192

def find_config(cwd: Path) -> Optional[Path]:

193

"""

194

Find pyproject.toml configuration file in directory hierarchy.

195

196

Args:

197

cwd (Path): Starting directory for search

198

199

Returns:

200

Optional[Path]: Path to config file if found, None otherwise

201

"""

202

203

def parse_config(path: Path) -> Mapping:

204

"""

205

Parse pytest-watcher configuration from pyproject.toml file.

206

207

Args:

208

path (Path): Path to pyproject.toml file

209

210

Returns:

211

Mapping: Configuration dictionary from [tool.pytest-watcher] section

212

213

Raises:

214

SystemExit: If file parsing fails or contains invalid options

215

"""

216

```

217

218

### File System Monitoring

219

220

Efficient file change detection using native OS APIs via the watchdog library.

221

222

```python { .api }

223

class EventHandler:

224

"""

225

Filesystem event handler for triggering test runs on file changes.

226

227

Monitors file creation, modification, deletion, and move events

228

matching specified patterns while ignoring excluded patterns.

229

"""

230

231

EVENTS_WATCHED = {

232

events.EVENT_TYPE_CREATED,

233

events.EVENT_TYPE_DELETED,

234

events.EVENT_TYPE_MODIFIED,

235

events.EVENT_TYPE_MOVED

236

}

237

238

def __init__(

239

self,

240

trigger: Trigger,

241

patterns: Optional[List[str]] = None,

242

ignore_patterns: Optional[List[str]] = None

243

):

244

"""

245

Initialize event handler with file patterns.

246

247

Args:

248

trigger (Trigger): Trigger instance for test execution

249

patterns (Optional[List[str]]): Unix-style patterns to watch (default: ["*.py"])

250

ignore_patterns (Optional[List[str]]): Unix-style patterns to ignore

251

"""

252

253

@property

254

def patterns(self) -> List[str]:

255

"""File patterns being watched."""

256

257

@property

258

def ignore_patterns(self) -> List[str]:

259

"""File patterns being ignored."""

260

261

def dispatch(self, event: events.FileSystemEvent) -> None:

262

"""

263

Handle filesystem event and trigger test run if pattern matches.

264

265

Args:

266

event (events.FileSystemEvent): Filesystem event from watchdog

267

"""

268

```

269

270

### Test Execution Triggering

271

272

Thread-safe test execution coordination with configurable delays.

273

274

```python { .api }

275

class Trigger:

276

"""

277

Thread-safe trigger for coordinating test execution timing.

278

279

Manages delayed test execution to accommodate file processing

280

tools like formatters that may modify files after save.

281

"""

282

283

def __init__(self, delay: float = 0.0):

284

"""

285

Initialize trigger with optional delay.

286

287

Args:

288

delay (float): Delay in seconds before test execution

289

"""

290

291

def emit(self) -> None:

292

"""Schedule test run with configured delay."""

293

294

def emit_now(self) -> None:

295

"""Schedule immediate test run without delay."""

296

297

def is_active(self) -> bool:

298

"""Check if trigger is currently active."""

299

300

def release(self) -> None:

301

"""Reset trigger to inactive state."""

302

303

def check(self) -> bool:

304

"""Check if trigger should fire based on timing."""

305

```

306

307

### Terminal Interface

308

309

Cross-platform terminal handling for interactive controls and display.

310

311

```python { .api }

312

class Terminal:

313

"""

314

Abstract base class for terminal interface implementations.

315

316

Provides screen clearing, message printing, menu display,

317

and keystroke capture for interactive control.

318

"""

319

320

def clear(self) -> None:

321

"""Clear the terminal screen."""

322

323

def print(self, msg: str) -> None:

324

"""Print message to terminal."""

325

326

def print_header(self, runner_args: List[str]) -> None:

327

"""Print header with current runner arguments."""

328

329

def print_short_menu(self, runner_args: List[str]) -> None:

330

"""Print abbreviated menu with key information."""

331

332

def print_menu(self, runner_args: List[str]) -> None:

333

"""Print full interactive menu with all available commands."""

334

335

def enter_capturing_mode(self) -> None:

336

"""Enable single keystroke capture mode."""

337

338

def capture_keystroke(self) -> Optional[str]:

339

"""Capture single keystroke without blocking."""

340

341

def reset(self) -> None:

342

"""Reset terminal to original state."""

343

344

class PosixTerminal(Terminal):

345

"""

346

POSIX-compatible terminal implementation with interactive features.

347

348

Supports keystroke capture, screen clearing, and terminal state management

349

on Unix-like systems.

350

"""

351

352

def __init__(self) -> None:

353

"""Initialize terminal and save initial state."""

354

355

class DummyTerminal(Terminal):

356

"""

357

Fallback terminal implementation for unsupported platforms.

358

359

Provides basic functionality without interactive features.

360

"""

361

362

def get_terminal() -> Terminal:

363

"""

364

Factory function to get appropriate Terminal implementation.

365

366

Returns:

367

Terminal: PosixTerminal on Unix systems, DummyTerminal on others

368

"""

369

```

370

371

### Interactive Command System

372

373

Pluggable command system for handling keyboard input during file watching.

374

375

```python { .api }

376

class Manager:

377

"""

378

Registry and dispatcher for interactive commands.

379

380

Manages keyboard shortcuts and command execution during interactive mode.

381

"""

382

383

@classmethod

384

def list_commands(cls) -> List[Command]:

385

"""Get list of all registered commands."""

386

387

@classmethod

388

def get_command(cls, character: str) -> Optional[Command]:

389

"""Get command by keyboard character."""

390

391

@classmethod

392

def run_command(

393

cls, character: str, trigger: Trigger, term: Terminal, config: Config

394

) -> None:

395

"""Execute command for given character input."""

396

397

class Command:

398

"""

399

Abstract base class for interactive commands.

400

401

Each command responds to a specific keyboard character and can

402

modify test execution or configuration state.

403

"""

404

405

character: str # Keyboard character that triggers this command

406

caption: str # Display name for menus

407

description: str # Description for help

408

show_in_menu: bool = True # Whether to show in interactive menu

409

410

def run(self, trigger: Trigger, term: Terminal, config: Config) -> None:

411

"""Execute the command with current context."""

412

```

413

414

### Argument Parsing

415

416

Command line argument parsing with support for passing through pytest arguments.

417

418

```python { .api }

419

def parse_arguments(args: Sequence[str]) -> Tuple[Namespace, List[str]]:

420

"""

421

Parse command line arguments for pytest-watcher.

422

423

Separates pytest-watcher specific arguments from arguments

424

that should be passed through to the test runner.

425

426

Args:

427

args (Sequence[str]): Command line arguments to parse

428

429

Returns:

430

Tuple[Namespace, List[str]]: Parsed namespace and runner arguments

431

"""

432

```

433

434

## Types

435

436

```python { .api }

437

from pathlib import Path

438

from typing import List, Optional, Mapping, Sequence, Tuple, Dict, Type

439

from argparse import Namespace

440

from watchdog import events

441

import abc

442

import threading

443

444

# Configuration constants

445

CONFIG_SECTION_NAME = "pytest-watcher"

446

CLI_FIELDS = {"now", "clear", "delay", "runner", "patterns", "ignore_patterns"}

447

CONFIG_FIELDS = CLI_FIELDS | {"runner_args"}

448

449

# Version and timing constants

450

VERSION = "0.4.3"

451

DEFAULT_DELAY = 0.2

452

LOOP_DELAY = 0.1

453

```

454

455

## Examples

456

457

### Basic File Watching

458

459

```python

460

# Start watching current directory

461

import subprocess

462

subprocess.run(["ptw", "."])

463

```

464

465

### Custom Configuration

466

467

Create `pyproject.toml`:

468

469

```toml

470

[tool.pytest-watcher]

471

now = true

472

clear = true

473

delay = 0.1

474

runner = "python -m pytest"

475

runner_args = ["--verbose", "--tb=short", "--durations=10"]

476

patterns = ["*.py", "*.pyi", "pyproject.toml"]

477

ignore_patterns = ["**/migrations/**", "**/venv/**", "**/__pycache__/**"]

478

```

479

480

Then run:

481

482

```bash

483

ptw src/

484

```

485

486

### Programmatic Usage

487

488

```python

489

from pytest_watcher import run

490

import sys

491

492

# Set up arguments as if from command line

493

sys.argv = ["pytest-watcher", ".", "--now", "--clear"]

494

495

# Start watching

496

run()

497

```

498

499

## Error Handling

500

501

Common error conditions:

502

503

- **Invalid path**: SystemExit if watch path doesn't exist

504

- **Config parsing errors**: SystemExit with detailed error message for malformed pyproject.toml

505

- **Terminal initialization**: Falls back to DummyTerminal on unsupported platforms

506

- **Filesystem monitoring**: Logs warnings for inaccessible files or directories

507

508

The tool is designed to be resilient and continue operation when possible, providing informative error messages when intervention is needed.