Library for managing git hooks using pyproject.toml configuration with an extensible plugin system
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Path matching and validation utilities for file processing in plugins. This API provides convenient functions for filtering and validating file paths based on patterns and file types.
Check if paths represent Python source files with proper extension matching.
def is_python_path(path: Optional[Path]) -> bool:
"""
Function to check if path is a Python file.
Args:
path: A path to a file
Returns:
True if path is a Python file (ends with .py), False otherwise
"""Usage Examples:
from autohooks.api.path import is_python_path
from autohooks.api.git import get_staged_status
from pathlib import Path
def precommit(config, report_progress, **kwargs):
# Get staged files
staged_files = get_staged_status()
# Filter for Python files using utility
python_files = []
for status_entry in staged_files:
if is_python_path(status_entry.path):
python_files.append(status_entry)
# Process Python files
for status_entry in python_files:
process_python_file(status_entry.absolute_path())
return 0
# Direct path checking
test_paths = [
Path("script.py"), # True
Path("module.py"), # True
Path("README.md"), # False
Path("config.toml"), # False
None # False
]
for path in test_paths:
result = is_python_path(path)
print(f"{path}: {result}")Match file paths against multiple patterns using fnmatch-style glob patterns with support for complex filtering rules.
def match(path: PathLike, pattern_list: Iterable[str]) -> bool:
"""
Check if a path like object matches to one of the patterns.
Internally fnmatch is used.
See https://docs.python.org/3/library/fnmatch.html for details.
Args:
path: PathLike to check if it matches to one of the patterns
pattern_list: Iterable (e.g tuple or list) of patterns to match against the path
Returns:
True if path matches a pattern of the list, False otherwise
"""Usage Examples:
from autohooks.api.path import match
from autohooks.api.git import get_staged_status
from pathlib import Path
def precommit(config, report_progress, **kwargs):
# Get staged files
staged_files = get_staged_status()
# Define patterns for different file types
python_patterns = ["*.py", "*.pyi"]
config_patterns = ["*.toml", "*.yaml", "*.yml", "*.json"]
documentation_patterns = ["*.md", "*.rst", "*.txt"]
python_files = []
config_files = []
doc_files = []
for status_entry in staged_files:
path = status_entry.path
if match(path, python_patterns):
python_files.append(status_entry)
elif match(path, config_patterns):
config_files.append(status_entry)
elif match(path, documentation_patterns):
doc_files.append(status_entry)
# Process different file types
if python_files:
info(f"Processing {len(python_files)} Python files")
if config_files:
info(f"Processing {len(config_files)} config files")
return 0
# Pattern matching examples
test_files = [
"src/main.py",
"tests/test_module.py",
"docs/README.md",
"pyproject.toml",
"requirements.txt"
]
# Match Python files
python_patterns = ["*.py", "**/*.py"]
for filepath in test_files:
if match(Path(filepath), python_patterns):
print(f"Python file: {filepath}")
# Match configuration files
config_patterns = ["*.toml", "*.yaml", "*.yml", "*.json", "*.ini"]
for filepath in test_files:
if match(Path(filepath), config_patterns):
print(f"Config file: {filepath}")
# Match by directory patterns
test_patterns = ["tests/**", "test_*"]
for filepath in test_files:
if match(Path(filepath), test_patterns):
print(f"Test file: {filepath}")Complex pattern matching scenarios for sophisticated file filtering.
Usage Examples:
from autohooks.api.path import match, is_python_path
from autohooks.api.git import get_staged_status
def precommit(config, report_progress, **kwargs):
staged_files = get_staged_status()
# Plugin configuration patterns
plugin_config = config.get("tool", "my-plugin")
include_patterns = plugin_config.get_value("include", ["*.py"])
exclude_patterns = plugin_config.get_value("exclude", [])
# Filter files based on patterns
target_files = []
for status_entry in staged_files:
path = status_entry.path
# Check include patterns
if match(path, include_patterns):
# Check exclude patterns
if exclude_patterns and match(path, exclude_patterns):
continue # Skip excluded files
target_files.append(status_entry)
# Advanced filtering combining utilities
python_files = [
entry for entry in target_files
if is_python_path(entry.path)
]
# Pattern-based exclusions
test_patterns = ["test_*.py", "*_test.py", "tests/**/*.py"]
non_test_files = [
entry for entry in python_files
if not match(entry.path, test_patterns)
]
return 0
# Complex pattern examples
complex_patterns = [
"src/**/*.py", # Python files in src directory tree
"!src/**/test_*.py", # Exclude test files (Note: fnmatch doesn't support !)
"*.{py,pyi}", # Python implementation and interface files
"[Mm]akefile*", # Makefiles with different cases
"config.{yml,yaml,json}" # Multiple config file extensions
]
# Note: For exclusion patterns, you need to handle them separately
include_patterns = ["src/**/*.py", "*.py"]
exclude_patterns = ["test_*.py", "*_test.py", "tests/**"]
def should_process_file(path: Path) -> bool:
"""Check if file should be processed based on include/exclude patterns."""
# Must match include patterns
if not match(path, include_patterns):
return False
# Must not match exclude patterns
if match(path, exclude_patterns):
return False
return TrueCombining path utilities with git operations for comprehensive file filtering.
Usage Examples:
from autohooks.api.path import is_python_path, match
from autohooks.api.git import get_staged_status, is_staged_status
def precommit(config, report_progress, **kwargs):
# Get all status entries
all_status = get_staged_status()
# Multi-stage filtering
processable_files = []
for status_entry in all_status:
# Only staged files
if not is_staged_status(status_entry):
continue
# Only Python files
if not is_python_path(status_entry.path):
continue
# Apply custom patterns
source_patterns = ["src/**/*.py", "lib/**/*.py"]
if not match(status_entry.path, source_patterns):
continue
# Exclude patterns
exclude_patterns = ["**/migrations/*.py", "**/__pycache__/**"]
if match(status_entry.path, exclude_patterns):
continue
processable_files.append(status_entry)
if not processable_files:
info("No Python source files to process")
return 0
report_progress.init(len(processable_files))
for status_entry in processable_files:
process_python_file(status_entry.absolute_path())
report_progress.update()
return 0The match function uses Python's fnmatch module, which supports Unix shell-style wildcards:
* - matches everything? - matches any single character[seq] - matches any character in seq[!seq] - matches any character not in seq** - matches directories recursively (when using pathlib)Pattern Examples:
patterns = [
"*.py", # All Python files
"test_*.py", # Test files starting with test_
"*_test.py", # Test files ending with _test
"src/**/*.py", # Python files in src directory tree
"*.{py,pyi}", # Multiple extensions (not standard fnmatch)
"[Tt]est*.py", # Files starting with Test or test
"config.?ml", # config.yml, config.xml, etc.
]from pathlib import Path
from typing import Iterable, Optional
from os import PathLikeInstall with Tessl CLI
npx tessl i tessl/pypi-autohooks