A comprehensive Python code quality checking tool that wraps PyFlakes, pycodestyle, and McCabe to provide unified static analysis with extensible plugin support.
—
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.
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())."""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."""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/'])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")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/'])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