Code audit tool for python
—
Pre-commit and post-commit hooks for Git and Mercurial integration. Pylama provides automated code quality checking as part of your version control workflow to catch issues before they enter the repository.
Git pre-commit hook implementation that checks staged files before allowing commits.
def git_hook(error: bool = True):
"""
Git pre-commit hook implementation.
Args:
error: Whether to exit with error code if issues are found
This function:
- Gets list of staged files using 'git diff-index --cached --name-only HEAD'
- Filters for Python files (.py extension)
- Runs pylama checks on staged files only
- Displays any errors found
- Exits with error code to prevent commit if issues found and error=True
Usage in pre-commit hook:
#!/usr/bin/env python
import sys
from pylama.hook import git_hook
if __name__ == '__main__':
sys.exit(git_hook())
"""Mercurial commit hook implementation for post-commit quality checking.
def hg_hook(_, repo, node=None, **kwargs):
"""
Mercurial commit hook implementation.
Args:
_: Unused first argument (Mercurial convention)
repo: Mercurial repository object
node: Starting revision node for the commit range
**kwargs: Additional keyword arguments from Mercurial
This function:
- Iterates through committed revisions from node to tip
- Collects all modified files in the commit range
- Filters for existing Python files
- Runs pylama checks on modified files
- Displays errors and exits with error code if issues found
Configured in .hg/hgrc:
[hooks]
commit = python:pylama.hook.hg_hook
qrefresh = python:pylama.hook.hg_hook
"""Automatic hook installation with VCS detection and setup.
def install_hook(path: str):
"""
Auto-detect VCS and install appropriate hook.
Args:
path: Repository root path to install hooks in
Detection logic:
- Checks for .git/hooks directory -> installs Git hook
- Checks for .hg directory -> installs Mercurial hook
- Exits with error if no supported VCS found
This function provides a convenient way to set up hooks without
manual VCS-specific configuration.
"""
def install_git(path: str):
"""
Install Git pre-commit hook.
Args:
path: Path to .git/hooks directory
Creates executable pre-commit hook script that:
- Imports and calls git_hook() function
- Has proper shebang and permissions
- Prevents commits when code quality issues are found
"""
def install_hg(path: str):
"""
Install Mercurial commit hook.
Args:
path: Path to .hg directory
Modifies .hg/hgrc configuration file to:
- Add [hooks] section if not present
- Configure commit and qrefresh hooks
- Point to pylama.hook.hg_hook function
"""Utility function for running shell commands from hooks.
def run(command: str) -> Tuple[int, List[bytes], List[bytes]]:
"""
Run shell command and capture output.
Args:
command: Shell command string to execute
Returns:
tuple: (return_code, stdout_lines, stderr_lines)
- return_code: Exit code from command
- stdout_lines: List of stdout lines as bytes
- stderr_lines: List of stderr lines as bytes
Used internally by hooks to execute Git/Mercurial commands
for file discovery and status checking.
"""# Install hooks via command line
import subprocess
# For Git repositories
subprocess.run(['pylama', '--hook', '/path/to/git/repo'])
# For Mercurial repositories
subprocess.run(['pylama', '--hook', '/path/to/hg/repo'])from pylama.hook import install_hook, install_git, install_hg
# Auto-detect and install
install_hook('/path/to/repository')
# Install specific VCS hooks
install_git('/path/to/repo/.git/hooks')
install_hg('/path/to/repo/.hg')#!/usr/bin/env python
# .git/hooks/pre-commit
import sys
from pylama.hook import git_hook
if __name__ == '__main__':
sys.exit(git_hook())# Make hook executable
chmod +x .git/hooks/pre-commit# .hg/hgrc
[hooks]
commit = python:pylama.hook.hg_hook
qrefresh = python:pylama.hook.hg_hookfrom pylama.hook import git_hook
# Run git hook manually (for testing)
try:
git_hook(error=False) # Don't exit on errors
print("All staged files pass quality checks")
except SystemExit as e:
if e.code != 0:
print("Quality issues found in staged files")import os
from pylama.hook import git_hook
from pylama.config import parse_options
def custom_git_hook():
"""Custom git hook with specific configuration."""
# Set custom pylama configuration
os.environ['PYLAMA_CONFIG'] = 'hooks/pylama.ini'
# Run hook with custom settings
git_hook()
# Custom configuration file (hooks/pylama.ini)
hook_config = """
[pylama]
linters = pycodestyle,pyflakes
ignore = E501,W503
format = parsable
"""import os
from pylama.hook import git_hook
def conditional_git_hook():
"""Run hook only in certain conditions."""
# Skip hook in CI environment
if os.getenv('CI') == 'true':
print("Skipping pylama hook in CI environment")
return 0
# Skip hook for merge commits
if os.path.exists('.git/MERGE_HEAD'):
print("Skipping pylama hook for merge commit")
return 0
# Run normal hook
return git_hook()import subprocess
from pylama.hook import run
from pylama.main import check_paths
from pylama.config import parse_options
def selective_git_hook():
"""Git hook that only checks certain file patterns."""
# Get staged files
_, files_modified, _ = run("git diff-index --cached --name-only HEAD")
# Filter for specific patterns
python_files = []
for file_bytes in files_modified:
filename = file_bytes.decode('utf-8')
if filename.endswith('.py') and not filename.startswith('tests/'):
python_files.append(filename)
if not python_files:
return 0
# Check filtered files
options = parse_options(['--linters=pycodestyle,pyflakes'])
errors = check_paths(python_files, options)
if errors:
for error in errors:
print(f"{error.filename}:{error.lnum} - {error.message}")
return 1
return 0import json
from pylama.hook import git_hook
from pylama.main import check_paths
from pylama.config import parse_options
def ci_hook():
"""Hook that generates CI-friendly output."""
try:
# Configure for JSON output
options = parse_options(['--format=json'])
# Get modified files (in CI, check all files)
errors = check_paths(['.'], options)
if errors:
# Output for CI system
error_data = [error.to_dict() for error in errors]
with open('pylama-results.json', 'w') as f:
json.dump(error_data, f, indent=2)
print(f"Found {len(errors)} code quality issues")
return 1
print("All files pass code quality checks")
return 0
except Exception as e:
print(f"Hook execution failed: {e}")
return 1import sys
from pylama.main import check_paths
from pylama.config import parse_options
def pre_push_hook():
"""Check all files before pushing to remote."""
print("Running code quality checks before push...")
# Check entire codebase
options = parse_options([
'--linters=pycodestyle,pyflakes,mccabe',
'--ignore=E501',
'.'
])
errors = check_paths(['.'], options)
if errors:
print(f"\nFound {len(errors)} code quality issues:")
for error in errors[:10]: # Show first 10 errors
print(f" {error.filename}:{error.lnum} - {error.message}")
if len(errors) > 10:
print(f" ... and {len(errors) - 10} more issues")
print("\nPush aborted. Fix issues before pushing.")
return 1
print("All files pass code quality checks. Push allowed.")
return 0
if __name__ == '__main__':
sys.exit(pre_push_hook())import os
import sys
from pylama.hook import git_hook
def bypassable_git_hook():
"""Git hook that can be bypassed with environment variable."""
# Check for bypass flag
if os.getenv('PYLAMA_SKIP_HOOK') == '1':
print("Pylama hook bypassed via PYLAMA_SKIP_HOOK")
return 0
# Check for --no-verify in git commit command
if '--no-verify' in ' '.join(sys.argv):
print("Pylama hook bypassed via --no-verify")
return 0
# Run normal hook
return git_hook()
# Usage: PYLAMA_SKIP_HOOK=1 git commit -m "emergency fix"
# Usage: git commit --no-verify -m "bypass hook"import time
from pylama.hook import run, git_hook
from pylama.main import check_paths
from pylama.config import parse_options
def fast_git_hook():
"""Optimized git hook for large repositories."""
start_time = time.time()
# Get only staged Python files
_, files_modified, _ = run("git diff-index --cached --name-only HEAD")
python_files = [
f.decode('utf-8') for f in files_modified
if f.decode('utf-8').endswith('.py')
]
if not python_files:
return 0
# Use fast linters only
options = parse_options([
'--linters=pycodestyle,pyflakes', # Skip slow linters
'--async', # Use parallel processing
'--ignore=E501,W503' # Ignore non-critical issues
])
errors = check_paths(python_files, options)
elapsed = time.time() - start_time
print(f"Hook completed in {elapsed:.2f}s")
if errors:
print(f"Found {len(errors)} issues in staged files")
for error in errors:
print(f" {error.filename}:{error.lnum} - {error.message}")
return 1
return 0# .pylama.ini (in repository root)
[pylama]
# Hook-specific configuration
linters = pycodestyle,pyflakes
ignore = E501,W503,E203
skip = migrations/*,venv/*
format = parsable
# Different settings for hooks vs manual runs
[pylama:hook]
linters = pycodestyle,pyflakes # Faster linters for hooks
max_line_length = 100# ~/.pylama.ini (global configuration)
[pylama]
# Global defaults for all projects
ignore = E501,W503
format = parsable
[pylama:hook]
# Specific settings when running from hooks
async = 1
concurrent = 1# Hook-specific environment variables
export PYLAMA_CONFIG="/path/to/hook-config.ini"
export PYLAMA_SKIP_HOOK="0"
export PYLAMA_HOOK_TIMEOUT="30"Install with Tessl CLI
npx tessl i tessl/pypi-pylama