or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

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

index.mddocs/

pytest-watcher

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.

Package Information

  • Package Name: pytest-watcher
  • Package Type: PyPI
  • Language: Python
  • Installation: pip install pytest-watcher
  • Python Compatibility: >= 3.7.0
  • License: MIT

Core Imports

For programmatic usage:

from pytest_watcher import run

Basic Usage

Command Line Interface

Basic usage with current directory:

ptw .

Watch specific directory:

ptw /path/to/project

Pass arguments to pytest:

ptw . -x --lf --nf

Advanced Usage

Use different test runner:

ptw . --runner tox

Watch specific file patterns:

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

Ignore certain patterns:

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

Run immediately and clear screen:

ptw . --now --clear

Custom delay before running tests:

ptw . --delay 0.5

Configuration

Configure via pyproject.toml:

[tool.pytest-watcher]
now = false
clear = true  
delay = 0.2
runner = "pytest"
runner_args = ["--verbose", "--tb=short"]
patterns = ["*.py"]
ignore_patterns = ["*/migrations/*", "*/venv/*"]

Capabilities

Command Line Interface

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

def run() -> None:
    """
    Main entry point that starts the file watcher and interactive loop.
    
    Parses command line arguments, sets up filesystem monitoring,
    and runs the interactive terminal interface until quit.
    
    Returns:
        None (runs until manually terminated)
    """

def main_loop(trigger: Trigger, config: Config, term: Terminal) -> None:
    """
    Execute one iteration of the main event loop.
    
    Checks for triggered test runs, executes tests if needed,
    captures keystrokes, and processes interactive commands.
    
    Args:
        trigger (Trigger): Test execution trigger
        config (Config): Current configuration
        term (Terminal): Terminal interface
    """

CLI Arguments:

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

Positional Arguments:
  path                    Path to watch for file changes

Optional Arguments:
  --now                   Trigger test run immediately on startup
  --clear                 Clear terminal screen before each test run  
  --delay DELAY           Delay in seconds before triggering test run (default: 0.2)
  --runner RUNNER         Executable for running tests (default: "pytest")
  --patterns PATTERNS     Comma-separated Unix-style file patterns to watch (default: "*.py")
  --ignore-patterns PATTERNS  Comma-separated Unix-style file patterns to ignore
  --version               Show version information

Interactive Controls

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

Interactive Commands:
  Enter    : Invoke test runner manually
  r        : Reset all runner arguments  
  c        : Change runner arguments interactively
  f        : Run only failed tests (add --lf flag)
  p        : Drop to pdb on failure (add --pdb flag)
  v        : Increase verbosity (add -v flag)
  e        : Erase terminal screen
  w        : Show full menu
  q        : Quit pytest-watcher

Configuration System

Configuration management with file-based and CLI override support.

class Config:
    """
    Configuration data class for pytest-watcher settings.
    
    Attributes:
        path (Path): Directory path to watch for changes
        now (bool): Whether to run tests immediately on startup  
        clear (bool): Whether to clear screen before test runs
        delay (float): Seconds to delay before triggering tests
        runner (str): Test runner executable name
        runner_args (List[str]): Additional arguments for test runner
        patterns (List[str]): File patterns to watch for changes
        ignore_patterns (List[str]): File patterns to ignore
    """
    
    @classmethod
    def create(cls, namespace: Namespace, extra_args: Optional[List[str]] = None) -> "Config":
        """
        Create Config instance from parsed arguments and configuration file.
        
        Args:
            namespace (Namespace): Parsed command line arguments
            extra_args (Optional[List[str]]): Additional runner arguments
            
        Returns:
            Config: Configured instance with merged settings
        """
def find_config(cwd: Path) -> Optional[Path]:
    """
    Find pyproject.toml configuration file in directory hierarchy.
    
    Args:
        cwd (Path): Starting directory for search
        
    Returns:
        Optional[Path]: Path to config file if found, None otherwise
    """

def parse_config(path: Path) -> Mapping:
    """
    Parse pytest-watcher configuration from pyproject.toml file.
    
    Args:
        path (Path): Path to pyproject.toml file
        
    Returns:
        Mapping: Configuration dictionary from [tool.pytest-watcher] section
        
    Raises:
        SystemExit: If file parsing fails or contains invalid options
    """

File System Monitoring

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

class EventHandler:
    """
    Filesystem event handler for triggering test runs on file changes.
    
    Monitors file creation, modification, deletion, and move events
    matching specified patterns while ignoring excluded patterns.
    """
    
    EVENTS_WATCHED = {
        events.EVENT_TYPE_CREATED,
        events.EVENT_TYPE_DELETED, 
        events.EVENT_TYPE_MODIFIED,
        events.EVENT_TYPE_MOVED
    }
    
    def __init__(
        self,
        trigger: Trigger,
        patterns: Optional[List[str]] = None,
        ignore_patterns: Optional[List[str]] = None
    ):
        """
        Initialize event handler with file patterns.
        
        Args:
            trigger (Trigger): Trigger instance for test execution
            patterns (Optional[List[str]]): Unix-style patterns to watch (default: ["*.py"])
            ignore_patterns (Optional[List[str]]): Unix-style patterns to ignore
        """
    
    @property
    def patterns(self) -> List[str]:
        """File patterns being watched."""
        
    @property 
    def ignore_patterns(self) -> List[str]:
        """File patterns being ignored."""
        
    def dispatch(self, event: events.FileSystemEvent) -> None:
        """
        Handle filesystem event and trigger test run if pattern matches.
        
        Args:
            event (events.FileSystemEvent): Filesystem event from watchdog
        """

Test Execution Triggering

Thread-safe test execution coordination with configurable delays.

class Trigger:
    """
    Thread-safe trigger for coordinating test execution timing.
    
    Manages delayed test execution to accommodate file processing
    tools like formatters that may modify files after save.
    """
    
    def __init__(self, delay: float = 0.0):
        """
        Initialize trigger with optional delay.
        
        Args:
            delay (float): Delay in seconds before test execution
        """
    
    def emit(self) -> None:
        """Schedule test run with configured delay."""
        
    def emit_now(self) -> None:
        """Schedule immediate test run without delay."""
        
    def is_active(self) -> bool:
        """Check if trigger is currently active."""
        
    def release(self) -> None:
        """Reset trigger to inactive state."""
        
    def check(self) -> bool:
        """Check if trigger should fire based on timing."""

Terminal Interface

Cross-platform terminal handling for interactive controls and display.

class Terminal:
    """
    Abstract base class for terminal interface implementations.
    
    Provides screen clearing, message printing, menu display,
    and keystroke capture for interactive control.
    """
    
    def clear(self) -> None:
        """Clear the terminal screen."""
        
    def print(self, msg: str) -> None:
        """Print message to terminal."""
        
    def print_header(self, runner_args: List[str]) -> None:
        """Print header with current runner arguments."""
        
    def print_short_menu(self, runner_args: List[str]) -> None:
        """Print abbreviated menu with key information."""
        
    def print_menu(self, runner_args: List[str]) -> None:
        """Print full interactive menu with all available commands."""
        
    def enter_capturing_mode(self) -> None:
        """Enable single keystroke capture mode."""
        
    def capture_keystroke(self) -> Optional[str]:
        """Capture single keystroke without blocking."""
        
    def reset(self) -> None:
        """Reset terminal to original state."""

class PosixTerminal(Terminal):
    """
    POSIX-compatible terminal implementation with interactive features.
    
    Supports keystroke capture, screen clearing, and terminal state management
    on Unix-like systems.
    """
    
    def __init__(self) -> None:
        """Initialize terminal and save initial state."""

class DummyTerminal(Terminal):
    """
    Fallback terminal implementation for unsupported platforms.
    
    Provides basic functionality without interactive features.
    """

def get_terminal() -> Terminal:
    """
    Factory function to get appropriate Terminal implementation.
    
    Returns:
        Terminal: PosixTerminal on Unix systems, DummyTerminal on others
    """

Interactive Command System

Pluggable command system for handling keyboard input during file watching.

class Manager:
    """
    Registry and dispatcher for interactive commands.
    
    Manages keyboard shortcuts and command execution during interactive mode.
    """
    
    @classmethod
    def list_commands(cls) -> List[Command]:
        """Get list of all registered commands."""
        
    @classmethod
    def get_command(cls, character: str) -> Optional[Command]:
        """Get command by keyboard character."""
        
    @classmethod
    def run_command(
        cls, character: str, trigger: Trigger, term: Terminal, config: Config
    ) -> None:
        """Execute command for given character input."""

class Command:
    """
    Abstract base class for interactive commands.
    
    Each command responds to a specific keyboard character and can
    modify test execution or configuration state.
    """
    
    character: str  # Keyboard character that triggers this command
    caption: str    # Display name for menus
    description: str  # Description for help
    show_in_menu: bool = True  # Whether to show in interactive menu
    
    def run(self, trigger: Trigger, term: Terminal, config: Config) -> None:
        """Execute the command with current context."""

Argument Parsing

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

def parse_arguments(args: Sequence[str]) -> Tuple[Namespace, List[str]]:
    """
    Parse command line arguments for pytest-watcher.
    
    Separates pytest-watcher specific arguments from arguments
    that should be passed through to the test runner.
    
    Args:
        args (Sequence[str]): Command line arguments to parse
        
    Returns:
        Tuple[Namespace, List[str]]: Parsed namespace and runner arguments
    """

Types

from pathlib import Path  
from typing import List, Optional, Mapping, Sequence, Tuple, Dict, Type
from argparse import Namespace
from watchdog import events
import abc
import threading

# Configuration constants
CONFIG_SECTION_NAME = "pytest-watcher"
CLI_FIELDS = {"now", "clear", "delay", "runner", "patterns", "ignore_patterns"}
CONFIG_FIELDS = CLI_FIELDS | {"runner_args"}

# Version and timing constants  
VERSION = "0.4.3"
DEFAULT_DELAY = 0.2
LOOP_DELAY = 0.1

Examples

Basic File Watching

# Start watching current directory
import subprocess
subprocess.run(["ptw", "."])

Custom Configuration

Create pyproject.toml:

[tool.pytest-watcher]
now = true
clear = true
delay = 0.1
runner = "python -m pytest"
runner_args = ["--verbose", "--tb=short", "--durations=10"]
patterns = ["*.py", "*.pyi", "pyproject.toml"]
ignore_patterns = ["**/migrations/**", "**/venv/**", "**/__pycache__/**"]

Then run:

ptw src/

Programmatic Usage

from pytest_watcher import run
import sys

# Set up arguments as if from command line
sys.argv = ["pytest-watcher", ".", "--now", "--clear"]

# Start watching
run()

Error Handling

Common error conditions:

  • Invalid path: SystemExit if watch path doesn't exist
  • Config parsing errors: SystemExit with detailed error message for malformed pyproject.toml
  • Terminal initialization: Falls back to DummyTerminal on unsupported platforms
  • Filesystem monitoring: Logs warnings for inaccessible files or directories

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