Trailrunner
Trailrunner is a simple Python library for walking paths on the filesystem and executing functions for each file found. It efficiently handles large codebases by respecting project-level `.gitignore` files and leveraging a process pool for concurrent function execution. It's designed for use by linting, formatting, and other developer tools that need to find and operate on all files in a project in a predictable fashion with a minimal API. The current version is 1.4.0, and it generally sees a few releases per year, often for bug fixes or minor feature additions.
Warnings
- breaking Python 3.6 support was dropped in Trailrunner v1.1.0 and again in v1.3.0. Users on Python 3.6 must upgrade to Python 3.7 or newer to use current versions of Trailrunner.
- gotcha As of v1.4.0, Trailrunner modified its path resolution behavior to "Always resolve and exclude paths relative to project root". This ensures that paths are walked and excluded in a manner more consistent with `git`'s behavior, preventing exclusions based on path segments outside the project root.
- gotcha Version 1.1.0 introduced a new, class-based API (`Trailrunner` class) internally. While the `walk()`, `run()`, and `walk_and_run()` functions continue to exist as simple wrappers around this new class, direct interaction with the `Trailrunner` class for advanced use cases (e.g., custom configuration or extensibility) might require familiarization with the new object-oriented structure.
Install
-
pip install trailrunner
Imports
- walk
from trailrunner import walk
- run
from trailrunner import run
- walk_and_run
from trailrunner import walk_and_run
Quickstart
import os
from pathlib import Path
from trailrunner import walk_and_run
# Create a dummy directory structure for demonstration
os.makedirs("my_project/src", exist_ok=True)
os.makedirs("my_project/tests", exist_ok=True)
with open("my_project/src/app.py", "w") as f:
f.write("print('Hello from app.py')")
with open("my_project/tests/test_app.py", "w") as f:
f.write("print('Running tests')")
with open("my_project/.gitignore", "w") as f:
f.write("*.tmp\n")
with open("my_project/temp.tmp", "w") as f:
f.write("temp file") # This should be ignored
def process_file(path: Path) -> str:
"""A simple function to process a file path."""
return f"Processed: {path.name}"
# Walk the 'my_project' directory and run 'process_file' on each non-ignored file
# trailrunner respects .gitignore for exclusions
results = walk_and_run([Path("my_project")], process_file)
print("--- Trailrunner Results ---")
for path, result in results.items():
print(f"{path}: {result}")
# Expected output for existing files, excluding 'temp.tmp'
# (Order may vary due to multiprocessing)
# my_project/src/app.py: Processed: app.py
# my_project/tests/test_app.py: Processed: test_app.py