CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pylama

Code audit tool for python

Pending
Overview
Eval results
Files

vcs-hooks.mddocs/

Version Control Hooks

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.

Capabilities

Git Integration

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 Integration

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
    """

Hook Installation

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
    """

Shell Command Execution

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.
    """

Installation and Setup

Command Line Installation

# 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'])

Programmatic Installation

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')

Manual Git Hook Setup

#!/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

Manual Mercurial Hook Setup

# .hg/hgrc
[hooks]
commit = python:pylama.hook.hg_hook
qrefresh = python:pylama.hook.hg_hook

Usage Examples

Basic Hook Usage

from 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")

Custom Hook Configuration

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
"""

Conditional Hook Execution

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()

Hook with Custom File Filtering

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 0

Hook Integration with CI/CD

import 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 1

Pre-push Hook

import 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())

Hook Bypass Mechanism

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"

Hook Performance Optimization

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

Hook Configuration

Repository-specific Settings

# .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

Global Hook Settings

# ~/.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

Environment Variables

# 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

docs

async-processing.md

configuration.md

error-processing.md

index.md

main-interface.md

plugin-development.md

pytest-integration.md

vcs-hooks.md

tile.json