CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-flake8

A comprehensive Python code quality checking tool that wraps PyFlakes, pycodestyle, and McCabe to provide unified static analysis with extensible plugin support.

Pending
Overview
Eval results
Files

formatting.mddocs/

Custom Formatting

Base classes and interfaces for creating custom output formatters to control how flake8 violations are displayed and reported. This enables integration with different tools and customized output formats.

Capabilities

Base Formatter Class

Abstract base class for all flake8 output formatters.

class BaseFormatter:
    """
    Base class defining the formatter interface for custom output formatting.
    
    All custom formatters must inherit from this class and implement the
    required methods to handle flake8 violation output formatting.
    """
    
    def __init__(self, options) -> None:
        """
        Initialize formatter with parsed options.
        
        This method is called automatically when the formatter is created.
        Subclasses should not need to override this unless they need custom
        initialization logic.
        
        Parameters:
        options: argparse.Namespace containing parsed command-line options
                 and configuration settings
        """
    
    def start(self) -> None:
        """
        Called when flake8 starts processing files.
        
        Override this method to perform any setup operations needed
        before violation reporting begins (e.g., opening files, writing
        headers, initializing data structures).
        
        Example:
        def start(self):
            if self.filename:
                self.output_fd = open(self.filename, 'w')
            print("Starting flake8 analysis...")
        """
    
    def stop(self) -> None:
        """
        Called when flake8 finishes processing all files.
        
        Override this method to perform cleanup operations after all
        violations have been reported (e.g., closing files, writing
        footers, finalizing reports).
        
        Example:
        def stop(self):
            if self.output_fd:
                self.output_fd.close()
            print("Analysis complete.")
        """
    
    def format(self, error) -> str | None:
        """
        Format a single violation for output.
        
        This is the core method that must be implemented by all custom
        formatters. It receives a Violation object and should return
        a formatted string representation.
        
        Parameters:
        error: Violation object containing error details
               - error.code: Error code (e.g., "E501")
               - error.filename: File path
               - error.line_number: Line number  
               - error.column_number: Column number
               - error.text: Error message
               
        Returns:
        str: Formatted string representation of the violation
        None: If the violation should be suppressed from output
        
        Example:
        def format(self, error):
            return f"{error.filename}:{error.line_number}: {error.code} {error.text}"
        """
    
    def show_statistics(self, statistics) -> None:
        """
        Display statistics summary after all files are processed.
        
        Override this method to customize how violation statistics
        are displayed at the end of flake8 execution.
        
        Parameters:
        statistics: Statistics object containing violation counts and summaries
        
        Example:
        def show_statistics(self, statistics):
            for stat in statistics.statistics:
                print(f"{stat.error_code}: {stat.count} occurrences")
        """
    
    def show_benchmarks(self, benchmarks) -> None:
        """
        Display performance benchmarks after execution.
        
        Override this method to customize how execution timing and
        performance metrics are displayed.
        
        Parameters:
        benchmarks: Benchmark data containing timing information
        """
    
    @property
    def options(self):
        """Access to parsed configuration options."""
    
    @property
    def filename(self) -> str | None:
        """Output filename if specified, None for stdout/stderr."""
    
    @property
    def output_fd(self):
        """File descriptor for output (set during start())."""

Built-in Formatter Examples

Reference implementations showing common formatting patterns.

class SimpleFormatter(BaseFormatter):
    """
    Abstract base class for simple line-based formatters.
    
    Provides common functionality for formatters that output one line
    per violation using a template string format.
    """
    
    error_format: str
    """
    Format string template for violation output.
    
    Must be defined by subclasses. Uses old-style Python string formatting
    with named parameters:
    - %(code)s: Error code
    - %(text)s: Error message
    - %(path)s: File path
    - %(row)d: Line number
    - %(col)d: Column number
    
    Example:
    error_format = "%(path)s:%(row)d:%(col)d: %(code)s %(text)s"
    """
    
    def format(self, error) -> str | None:
        """Format violation using the error_format template."""

Custom Formatter Examples

JSON Formatter

from flake8.formatting.base import BaseFormatter
import json

class JSONFormatter(BaseFormatter):
    """Output flake8 violations in JSON format."""
    
    def __init__(self, options):
        super().__init__(options)
        self.violations = []
    
    def start(self):
        """Initialize violation collection."""
        self.violations = []
        if self.filename:
            self.output_fd = open(self.filename, 'w')
    
    def format(self, error):
        """Collect violation data instead of immediate output."""
        violation_data = {
            'file': error.filename,
            'line': error.line_number,
            'column': error.column_number,
            'code': error.code,
            'message': error.text,
            'physical_line': error.physical_line
        }
        self.violations.append(violation_data)
        return None  # Suppress immediate output
    
    def stop(self):
        """Output collected violations as JSON."""
        output_data = {
            'flake8_version': '7.3.0',
            'violations': self.violations,
            'total_violations': len(self.violations)
        }
        
        json_output = json.dumps(output_data, indent=2)
        
        if self.output_fd:
            self.output_fd.write(json_output)
            self.output_fd.close()
        else:
            print(json_output)
    
    def show_statistics(self, statistics):
        """Statistics are included in final JSON output."""
        pass  # Already handled in stop()

# Usage with programmatic API
from flake8.api import legacy

style_guide = legacy.get_style_guide()
style_guide.init_report(JSONFormatter)
report = style_guide.check_files(['myproject/'])

HTML Report Formatter

from flake8.formatting.base import BaseFormatter
from html import escape
import os

class HTMLFormatter(BaseFormatter):
    """Generate HTML report with violation details."""
    
    def __init__(self, options):
        super().__init__(options)
        self.violations_by_file = {}
    
    def start(self):
        """Start HTML document."""
        if self.filename:
            self.output_fd = open(self.filename, 'w')
        
        html_header = """
        <!DOCTYPE html>
        <html>
        <head>
            <title>Flake8 Code Quality Report</title>
            <style>
                body { font-family: Arial, sans-serif; margin: 20px; }
                .file-section { margin-bottom: 30px; border: 1px solid #ddd; padding: 15px; }
                .violation { margin: 5px 0; padding: 5px; background: #f5f5f5; }
                .error { border-left: 4px solid #ff4444; }
                .warning { border-left: 4px solid #ffaa00; }
                .code { font-family: monospace; font-weight: bold; }
                .summary { background: #e8f4f8; padding: 15px; margin-bottom: 20px; }
            </style>
        </head>
        <body>
            <h1>Flake8 Code Quality Report</h1>
        """
        
        if self.output_fd:
            self.output_fd.write(html_header)
        else:
            print(html_header)
    
    def format(self, error):
        """Collect violations by file."""
        filename = error.filename
        if filename not in self.violations_by_file:
            self.violations_by_file[filename] = []
        
        self.violations_by_file[filename].append(error)
        return None  # Suppress immediate output
    
    def stop(self):
        """Generate complete HTML report."""
        total_violations = sum(len(violations) for violations in self.violations_by_file.values())
        
        # Summary section
        html_content = f"""
            <div class="summary">
                <h2>Summary</h2>
                <p><strong>Total Files:</strong> {len(self.violations_by_file)}</p>
                <p><strong>Total Violations:</strong> {total_violations}</p>
            </div>
        """
        
        # Violations by file
        for filename, violations in self.violations_by_file.items():
            html_content += f"""
            <div class="file-section">
                <h3>{escape(filename)} ({len(violations)} violations)</h3>
            """
            
            for violation in violations:
                violation_class = "error" if violation.code.startswith('E') else "warning"
                html_content += f"""
                <div class="violation {violation_class}">
                    <span class="code">{escape(violation.code)}</span>
                    Line {violation.line_number}, Column {violation.column_number}:
                    {escape(violation.text)}
                </div>
                """
            
            html_content += "</div>"
        
        html_footer = """
        </body>
        </html>
        """
        
        full_html = html_content + html_footer
        
        if self.output_fd:
            self.output_fd.write(full_html)
            self.output_fd.close()
        else:
            print(full_html)

# Usage
style_guide = legacy.get_style_guide(output_file='report.html')
style_guide.init_report(HTMLFormatter)
report = style_guide.check_files(['myproject/'])
print("HTML report generated: report.html")

Team Dashboard Formatter

from flake8.formatting.base import BaseFormatter
import requests
import json
from datetime import datetime

class TeamDashboardFormatter(BaseFormatter):
    """Send flake8 results to team dashboard API."""
    
    def __init__(self, options):
        super().__init__(options)
        self.violations = []
        self.start_time = datetime.now()
        
        # Get dashboard config from environment or options
        self.dashboard_url = getattr(options, 'dashboard_url', 
                                   os.environ.get('FLAKE8_DASHBOARD_URL'))
        self.project_name = getattr(options, 'project_name',
                                  os.environ.get('PROJECT_NAME', 'unknown'))
    
    def format(self, error):
        """Collect violation data for dashboard."""
        self.violations.append({
            'file': error.filename,
            'line': error.line_number,
            'column': error.column_number,
            'code': error.code,
            'message': error.text,
            'severity': 'error' if error.code.startswith('E') else 'warning'
        })
        
        # Also output to console
        return f"{error.filename}:{error.line_number}:{error.column_number}: {error.code} {error.text}"
    
    def stop(self):
        """Send results to team dashboard."""
        if not self.dashboard_url:
            print("Warning: No dashboard URL configured")
            return
        
        end_time = datetime.now()
        duration = (end_time - self.start_time).total_seconds()
        
        # Prepare data for dashboard
        dashboard_data = {
            'project': self.project_name,
            'timestamp': self.start_time.isoformat(),
            'duration_seconds': duration,
            'total_violations': len(self.violations),
            'error_count': len([v for v in self.violations if v['severity'] == 'error']),
            'warning_count': len([v for v in self.violations if v['severity'] == 'warning']),
            'violations': self.violations,
            'files_checked': len(set(v['file'] for v in self.violations)),
            'flake8_version': '7.3.0'
        }
        
        try:
            response = requests.post(
                f"{self.dashboard_url}/api/flake8-results",
                json=dashboard_data,
                headers={'Content-Type': 'application/json'},
                timeout=30
            )
            
            if response.status_code == 200:
                print(f"✅ Results sent to team dashboard: {self.dashboard_url}")
                dashboard_response = response.json()
                if 'report_url' in dashboard_response:
                    print(f"📊 View report: {dashboard_response['report_url']}")
            else:
                print(f"⚠️  Dashboard upload failed: {response.status_code}")
                
        except requests.RequestException as e:
            print(f"❌ Failed to send results to dashboard: {e}")
    
    def show_statistics(self, statistics):
        """Display local statistics summary."""
        print(f"\n📈 Quality Summary for {self.project_name}:")
        print(f"   Total violations: {len(self.violations)}")
        print(f"   Files affected: {len(set(v['file'] for v in self.violations))}")
        
        # Group by error code
        code_counts = {}
        for violation in self.violations:
            code = violation['code']
            code_counts[code] = code_counts.get(code, 0) + 1
        
        print("   Top violation types:")
        for code, count in sorted(code_counts.items(), key=lambda x: x[1], reverse=True)[:5]:
            print(f"     {code}: {count}")

# Usage in CI/CD pipeline
import os
os.environ['FLAKE8_DASHBOARD_URL'] = 'https://dashboard.example.com'
os.environ['PROJECT_NAME'] = 'my-python-project'

style_guide = legacy.get_style_guide()
style_guide.init_report(TeamDashboardFormatter)
report = style_guide.check_files(['src/', 'tests/'])

Custom Color Formatter

from flake8.formatting.base import BaseFormatter

class ColorFormatter(BaseFormatter):
    """Formatter with custom color coding for different violation types."""
    
    # ANSI color codes
    COLORS = {
        'red': '\033[91m',
        'yellow': '\033[93m',
        'blue': '\033[94m',
        'green': '\033[92m',
        'cyan': '\033[96m',
        'bold': '\033[1m',
        'reset': '\033[0m'
    }
    
    def __init__(self, options):
        super().__init__(options)
        # Disable colors if outputting to file or if requested
        self.use_colors = (not self.filename and 
                          getattr(options, 'color', 'auto') != 'never' and
                          hasattr(sys.stdout, 'isatty') and sys.stdout.isatty())
    
    def colorize(self, text, color):
        """Apply color to text if colors are enabled."""
        if not self.use_colors:
            return text
        return f"{self.COLORS.get(color, '')}{text}{self.COLORS['reset']}"
    
    def format(self, error):
        """Format violation with appropriate colors."""
        # Choose color based on error type
        if error.code.startswith('E'):
            # Style errors in red
            code_color = 'red'
            severity = 'ERROR'
        elif error.code.startswith('W'):
            # Style warnings in yellow
            code_color = 'yellow'
            severity = 'WARN'
        elif error.code.startswith('F'):
            # Style flakes errors in cyan
            code_color = 'cyan'
            severity = 'FLAKE'
        else:
            # Other codes in blue
            code_color = 'blue'
            severity = 'OTHER'
        
        # Format components with colors
        filename = self.colorize(error.filename, 'bold')
        line_col = self.colorize(f"{error.line_number}:{error.column_number}", 'green')
        code = self.colorize(error.code, code_color)
        severity_label = self.colorize(f"[{severity}]", code_color)
        
        return f"{filename}:{line_col} {severity_label} {code} {error.text}"
    
    def show_statistics(self, statistics):
        """Show colorized statistics summary."""
        if not hasattr(statistics, 'statistics'):
            return
            
        print(self.colorize("\n📊 Violation Statistics:", 'bold'))
        
        for stat in statistics.statistics:
            count_color = 'red' if stat.count > 10 else 'yellow' if stat.count > 5 else 'green'
            count_str = self.colorize(str(stat.count), count_color)
            code_str = self.colorize(stat.error_code, 'cyan')
            print(f"   {count_str} × {code_str} {stat.message}")

# Usage
import sys
style_guide = legacy.get_style_guide(color='always', statistics=True)
style_guide.init_report(ColorFormatter)
report = style_guide.check_files(['myproject/'])

Install with Tessl CLI

npx tessl i tessl/pypi-flake8

docs

cli.md

config-logging.md

exceptions.md

formatting.md

index.md

programmatic-api.md

tile.json