or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli.mdconfig-logging.mdexceptions.mdformatting.mdindex.mdprogrammatic-api.md

formatting.mddocs/

0

# Custom Formatting

1

2

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.

3

4

## Capabilities

5

6

### Base Formatter Class

7

8

Abstract base class for all flake8 output formatters.

9

10

```python { .api }

11

class BaseFormatter:

12

"""

13

Base class defining the formatter interface for custom output formatting.

14

15

All custom formatters must inherit from this class and implement the

16

required methods to handle flake8 violation output formatting.

17

"""

18

19

def __init__(self, options) -> None:

20

"""

21

Initialize formatter with parsed options.

22

23

This method is called automatically when the formatter is created.

24

Subclasses should not need to override this unless they need custom

25

initialization logic.

26

27

Parameters:

28

options: argparse.Namespace containing parsed command-line options

29

and configuration settings

30

"""

31

32

def start(self) -> None:

33

"""

34

Called when flake8 starts processing files.

35

36

Override this method to perform any setup operations needed

37

before violation reporting begins (e.g., opening files, writing

38

headers, initializing data structures).

39

40

Example:

41

def start(self):

42

if self.filename:

43

self.output_fd = open(self.filename, 'w')

44

print("Starting flake8 analysis...")

45

"""

46

47

def stop(self) -> None:

48

"""

49

Called when flake8 finishes processing all files.

50

51

Override this method to perform cleanup operations after all

52

violations have been reported (e.g., closing files, writing

53

footers, finalizing reports).

54

55

Example:

56

def stop(self):

57

if self.output_fd:

58

self.output_fd.close()

59

print("Analysis complete.")

60

"""

61

62

def format(self, error) -> str | None:

63

"""

64

Format a single violation for output.

65

66

This is the core method that must be implemented by all custom

67

formatters. It receives a Violation object and should return

68

a formatted string representation.

69

70

Parameters:

71

error: Violation object containing error details

72

- error.code: Error code (e.g., "E501")

73

- error.filename: File path

74

- error.line_number: Line number

75

- error.column_number: Column number

76

- error.text: Error message

77

78

Returns:

79

str: Formatted string representation of the violation

80

None: If the violation should be suppressed from output

81

82

Example:

83

def format(self, error):

84

return f"{error.filename}:{error.line_number}: {error.code} {error.text}"

85

"""

86

87

def show_statistics(self, statistics) -> None:

88

"""

89

Display statistics summary after all files are processed.

90

91

Override this method to customize how violation statistics

92

are displayed at the end of flake8 execution.

93

94

Parameters:

95

statistics: Statistics object containing violation counts and summaries

96

97

Example:

98

def show_statistics(self, statistics):

99

for stat in statistics.statistics:

100

print(f"{stat.error_code}: {stat.count} occurrences")

101

"""

102

103

def show_benchmarks(self, benchmarks) -> None:

104

"""

105

Display performance benchmarks after execution.

106

107

Override this method to customize how execution timing and

108

performance metrics are displayed.

109

110

Parameters:

111

benchmarks: Benchmark data containing timing information

112

"""

113

114

@property

115

def options(self):

116

"""Access to parsed configuration options."""

117

118

@property

119

def filename(self) -> str | None:

120

"""Output filename if specified, None for stdout/stderr."""

121

122

@property

123

def output_fd(self):

124

"""File descriptor for output (set during start())."""

125

```

126

127

### Built-in Formatter Examples

128

129

Reference implementations showing common formatting patterns.

130

131

```python { .api }

132

class SimpleFormatter(BaseFormatter):

133

"""

134

Abstract base class for simple line-based formatters.

135

136

Provides common functionality for formatters that output one line

137

per violation using a template string format.

138

"""

139

140

error_format: str

141

"""

142

Format string template for violation output.

143

144

Must be defined by subclasses. Uses old-style Python string formatting

145

with named parameters:

146

- %(code)s: Error code

147

- %(text)s: Error message

148

- %(path)s: File path

149

- %(row)d: Line number

150

- %(col)d: Column number

151

152

Example:

153

error_format = "%(path)s:%(row)d:%(col)d: %(code)s %(text)s"

154

"""

155

156

def format(self, error) -> str | None:

157

"""Format violation using the error_format template."""

158

```

159

160

## Custom Formatter Examples

161

162

### JSON Formatter

163

164

```python

165

from flake8.formatting.base import BaseFormatter

166

import json

167

168

class JSONFormatter(BaseFormatter):

169

"""Output flake8 violations in JSON format."""

170

171

def __init__(self, options):

172

super().__init__(options)

173

self.violations = []

174

175

def start(self):

176

"""Initialize violation collection."""

177

self.violations = []

178

if self.filename:

179

self.output_fd = open(self.filename, 'w')

180

181

def format(self, error):

182

"""Collect violation data instead of immediate output."""

183

violation_data = {

184

'file': error.filename,

185

'line': error.line_number,

186

'column': error.column_number,

187

'code': error.code,

188

'message': error.text,

189

'physical_line': error.physical_line

190

}

191

self.violations.append(violation_data)

192

return None # Suppress immediate output

193

194

def stop(self):

195

"""Output collected violations as JSON."""

196

output_data = {

197

'flake8_version': '7.3.0',

198

'violations': self.violations,

199

'total_violations': len(self.violations)

200

}

201

202

json_output = json.dumps(output_data, indent=2)

203

204

if self.output_fd:

205

self.output_fd.write(json_output)

206

self.output_fd.close()

207

else:

208

print(json_output)

209

210

def show_statistics(self, statistics):

211

"""Statistics are included in final JSON output."""

212

pass # Already handled in stop()

213

214

# Usage with programmatic API

215

from flake8.api import legacy

216

217

style_guide = legacy.get_style_guide()

218

style_guide.init_report(JSONFormatter)

219

report = style_guide.check_files(['myproject/'])

220

```

221

222

### HTML Report Formatter

223

224

```python

225

from flake8.formatting.base import BaseFormatter

226

from html import escape

227

import os

228

229

class HTMLFormatter(BaseFormatter):

230

"""Generate HTML report with violation details."""

231

232

def __init__(self, options):

233

super().__init__(options)

234

self.violations_by_file = {}

235

236

def start(self):

237

"""Start HTML document."""

238

if self.filename:

239

self.output_fd = open(self.filename, 'w')

240

241

html_header = """

242

<!DOCTYPE html>

243

<html>

244

<head>

245

<title>Flake8 Code Quality Report</title>

246

<style>

247

body { font-family: Arial, sans-serif; margin: 20px; }

248

.file-section { margin-bottom: 30px; border: 1px solid #ddd; padding: 15px; }

249

.violation { margin: 5px 0; padding: 5px; background: #f5f5f5; }

250

.error { border-left: 4px solid #ff4444; }

251

.warning { border-left: 4px solid #ffaa00; }

252

.code { font-family: monospace; font-weight: bold; }

253

.summary { background: #e8f4f8; padding: 15px; margin-bottom: 20px; }

254

</style>

255

</head>

256

<body>

257

<h1>Flake8 Code Quality Report</h1>

258

"""

259

260

if self.output_fd:

261

self.output_fd.write(html_header)

262

else:

263

print(html_header)

264

265

def format(self, error):

266

"""Collect violations by file."""

267

filename = error.filename

268

if filename not in self.violations_by_file:

269

self.violations_by_file[filename] = []

270

271

self.violations_by_file[filename].append(error)

272

return None # Suppress immediate output

273

274

def stop(self):

275

"""Generate complete HTML report."""

276

total_violations = sum(len(violations) for violations in self.violations_by_file.values())

277

278

# Summary section

279

html_content = f"""

280

<div class="summary">

281

<h2>Summary</h2>

282

<p><strong>Total Files:</strong> {len(self.violations_by_file)}</p>

283

<p><strong>Total Violations:</strong> {total_violations}</p>

284

</div>

285

"""

286

287

# Violations by file

288

for filename, violations in self.violations_by_file.items():

289

html_content += f"""

290

<div class="file-section">

291

<h3>{escape(filename)} ({len(violations)} violations)</h3>

292

"""

293

294

for violation in violations:

295

violation_class = "error" if violation.code.startswith('E') else "warning"

296

html_content += f"""

297

<div class="violation {violation_class}">

298

<span class="code">{escape(violation.code)}</span>

299

Line {violation.line_number}, Column {violation.column_number}:

300

{escape(violation.text)}

301

</div>

302

"""

303

304

html_content += "</div>"

305

306

html_footer = """

307

</body>

308

</html>

309

"""

310

311

full_html = html_content + html_footer

312

313

if self.output_fd:

314

self.output_fd.write(full_html)

315

self.output_fd.close()

316

else:

317

print(full_html)

318

319

# Usage

320

style_guide = legacy.get_style_guide(output_file='report.html')

321

style_guide.init_report(HTMLFormatter)

322

report = style_guide.check_files(['myproject/'])

323

print("HTML report generated: report.html")

324

```

325

326

### Team Dashboard Formatter

327

328

```python

329

from flake8.formatting.base import BaseFormatter

330

import requests

331

import json

332

from datetime import datetime

333

334

class TeamDashboardFormatter(BaseFormatter):

335

"""Send flake8 results to team dashboard API."""

336

337

def __init__(self, options):

338

super().__init__(options)

339

self.violations = []

340

self.start_time = datetime.now()

341

342

# Get dashboard config from environment or options

343

self.dashboard_url = getattr(options, 'dashboard_url',

344

os.environ.get('FLAKE8_DASHBOARD_URL'))

345

self.project_name = getattr(options, 'project_name',

346

os.environ.get('PROJECT_NAME', 'unknown'))

347

348

def format(self, error):

349

"""Collect violation data for dashboard."""

350

self.violations.append({

351

'file': error.filename,

352

'line': error.line_number,

353

'column': error.column_number,

354

'code': error.code,

355

'message': error.text,

356

'severity': 'error' if error.code.startswith('E') else 'warning'

357

})

358

359

# Also output to console

360

return f"{error.filename}:{error.line_number}:{error.column_number}: {error.code} {error.text}"

361

362

def stop(self):

363

"""Send results to team dashboard."""

364

if not self.dashboard_url:

365

print("Warning: No dashboard URL configured")

366

return

367

368

end_time = datetime.now()

369

duration = (end_time - self.start_time).total_seconds()

370

371

# Prepare data for dashboard

372

dashboard_data = {

373

'project': self.project_name,

374

'timestamp': self.start_time.isoformat(),

375

'duration_seconds': duration,

376

'total_violations': len(self.violations),

377

'error_count': len([v for v in self.violations if v['severity'] == 'error']),

378

'warning_count': len([v for v in self.violations if v['severity'] == 'warning']),

379

'violations': self.violations,

380

'files_checked': len(set(v['file'] for v in self.violations)),

381

'flake8_version': '7.3.0'

382

}

383

384

try:

385

response = requests.post(

386

f"{self.dashboard_url}/api/flake8-results",

387

json=dashboard_data,

388

headers={'Content-Type': 'application/json'},

389

timeout=30

390

)

391

392

if response.status_code == 200:

393

print(f"βœ… Results sent to team dashboard: {self.dashboard_url}")

394

dashboard_response = response.json()

395

if 'report_url' in dashboard_response:

396

print(f"πŸ“Š View report: {dashboard_response['report_url']}")

397

else:

398

print(f"⚠️ Dashboard upload failed: {response.status_code}")

399

400

except requests.RequestException as e:

401

print(f"❌ Failed to send results to dashboard: {e}")

402

403

def show_statistics(self, statistics):

404

"""Display local statistics summary."""

405

print(f"\nπŸ“ˆ Quality Summary for {self.project_name}:")

406

print(f" Total violations: {len(self.violations)}")

407

print(f" Files affected: {len(set(v['file'] for v in self.violations))}")

408

409

# Group by error code

410

code_counts = {}

411

for violation in self.violations:

412

code = violation['code']

413

code_counts[code] = code_counts.get(code, 0) + 1

414

415

print(" Top violation types:")

416

for code, count in sorted(code_counts.items(), key=lambda x: x[1], reverse=True)[:5]:

417

print(f" {code}: {count}")

418

419

# Usage in CI/CD pipeline

420

import os

421

os.environ['FLAKE8_DASHBOARD_URL'] = 'https://dashboard.example.com'

422

os.environ['PROJECT_NAME'] = 'my-python-project'

423

424

style_guide = legacy.get_style_guide()

425

style_guide.init_report(TeamDashboardFormatter)

426

report = style_guide.check_files(['src/', 'tests/'])

427

```

428

429

### Custom Color Formatter

430

431

```python

432

from flake8.formatting.base import BaseFormatter

433

434

class ColorFormatter(BaseFormatter):

435

"""Formatter with custom color coding for different violation types."""

436

437

# ANSI color codes

438

COLORS = {

439

'red': '\033[91m',

440

'yellow': '\033[93m',

441

'blue': '\033[94m',

442

'green': '\033[92m',

443

'cyan': '\033[96m',

444

'bold': '\033[1m',

445

'reset': '\033[0m'

446

}

447

448

def __init__(self, options):

449

super().__init__(options)

450

# Disable colors if outputting to file or if requested

451

self.use_colors = (not self.filename and

452

getattr(options, 'color', 'auto') != 'never' and

453

hasattr(sys.stdout, 'isatty') and sys.stdout.isatty())

454

455

def colorize(self, text, color):

456

"""Apply color to text if colors are enabled."""

457

if not self.use_colors:

458

return text

459

return f"{self.COLORS.get(color, '')}{text}{self.COLORS['reset']}"

460

461

def format(self, error):

462

"""Format violation with appropriate colors."""

463

# Choose color based on error type

464

if error.code.startswith('E'):

465

# Style errors in red

466

code_color = 'red'

467

severity = 'ERROR'

468

elif error.code.startswith('W'):

469

# Style warnings in yellow

470

code_color = 'yellow'

471

severity = 'WARN'

472

elif error.code.startswith('F'):

473

# Style flakes errors in cyan

474

code_color = 'cyan'

475

severity = 'FLAKE'

476

else:

477

# Other codes in blue

478

code_color = 'blue'

479

severity = 'OTHER'

480

481

# Format components with colors

482

filename = self.colorize(error.filename, 'bold')

483

line_col = self.colorize(f"{error.line_number}:{error.column_number}", 'green')

484

code = self.colorize(error.code, code_color)

485

severity_label = self.colorize(f"[{severity}]", code_color)

486

487

return f"{filename}:{line_col} {severity_label} {code} {error.text}"

488

489

def show_statistics(self, statistics):

490

"""Show colorized statistics summary."""

491

if not hasattr(statistics, 'statistics'):

492

return

493

494

print(self.colorize("\nπŸ“Š Violation Statistics:", 'bold'))

495

496

for stat in statistics.statistics:

497

count_color = 'red' if stat.count > 10 else 'yellow' if stat.count > 5 else 'green'

498

count_str = self.colorize(str(stat.count), count_color)

499

code_str = self.colorize(stat.error_code, 'cyan')

500

print(f" {count_str} Γ— {code_str} {stat.message}")

501

502

# Usage

503

import sys

504

style_guide = legacy.get_style_guide(color='always', statistics=True)

505

style_guide.init_report(ColorFormatter)

506

report = style_guide.check_files(['myproject/'])

507

```