or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

tessl/pypi-flake8-bugbear

A plugin for flake8 finding likely bugs and design problems in your program.

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
pypipkg:pypi/flake8-bugbear@24.12.x

To install, run

npx @tessl/cli install tessl/pypi-flake8-bugbear@24.12.0

0

# flake8-bugbear

1

2

A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle. This plugin implements over 70 opinionated linting rules that catch subtle bugs, design problems, and code quality issues that standard tools miss.

3

4

## Package Information

5

6

- **Package Name**: flake8-bugbear

7

- **Language**: Python

8

- **Installation**: `pip install flake8-bugbear`

9

10

## Core Imports

11

12

The plugin automatically integrates with flake8 once installed. For programmatic usage:

13

14

```python

15

from bugbear import BugBearChecker

16

```

17

18

## Basic Usage

19

20

### As a flake8 plugin (standard usage)

21

22

Once installed, flake8-bugbear automatically runs as part of flake8:

23

24

```bash

25

# Install the plugin

26

pip install flake8-bugbear

27

28

# Run flake8 - bugbear rules are automatically included

29

flake8 mycode.py

30

31

# Check that the plugin is loaded

32

flake8 --version

33

```

34

35

### Programmatic usage

36

37

```python

38

import ast

39

from bugbear import BugBearChecker

40

41

# Parse some Python code

42

code = '''

43

def example():

44

try:

45

risky_operation()

46

except: # B001: bare except

47

pass

48

'''

49

50

tree = ast.parse(code)

51

lines = code.splitlines()

52

53

# Create and run the checker

54

checker = BugBearChecker(tree=tree, filename="example.py", lines=lines)

55

errors = list(checker.run())

56

57

for error in errors:

58

print(f"{error.lineno}:{error.col} {error.message}")

59

```

60

61

## Architecture

62

63

flake8-bugbear follows flake8's plugin architecture:

64

65

- **BugBearChecker**: Main plugin class implementing flake8's checker interface

66

- **BugBearVisitor**: AST visitor that traverses code and detects rule violations

67

- **Error definitions**: 70+ predefined error types with specific codes (B001-B950)

68

- **Configuration options**: Support for extending rules and customizing behavior

69

70

The plugin registers with flake8 via entry point: `flake8.extension = {B = "bugbear:BugBearChecker"}`

71

72

## Configuration Options

73

74

flake8-bugbear supports the following configuration options:

75

76

```ini

77

[flake8]

78

# Extend the list of immutable function calls for B008/B039

79

extend-immutable-calls = frozenset,tuple

80

81

# Customize classmethod decorators for B902

82

classmethod-decorators = classmethod,my_classmethod_decorator

83

```

84

85

## Capabilities

86

87

### Main Plugin Interface

88

89

The BugBearChecker class provides the primary interface for flake8 integration and programmatic usage.

90

91

```python { .api }

92

class BugBearChecker:

93

"""

94

Main checker class implementing flake8 plugin interface.

95

96

Attributes:

97

name (str): Plugin name "flake8-bugbear"

98

version (str): Plugin version

99

tree (ast.AST): AST tree to analyze

100

filename (str): Name of file being analyzed

101

lines (List[str]): Source code lines

102

max_line_length (int): Maximum line length setting

103

options (object): Configuration options

104

"""

105

106

def run(self):

107

"""

108

Main entry point that yields error instances.

109

110

Yields:

111

Error objects with line number, column, and message

112

"""

113

114

def load_file(self) -> None:

115

"""Load file content into tree and lines attributes."""

116

117

def gen_line_based_checks(self):

118

"""

119

Generator for line-based lint checks.

120

121

Yields:

122

Error objects for line-based violations

123

"""

124

125

@classmethod

126

def adapt_error(cls, e):

127

"""

128

Adapt extended error namedtuple to be compatible with flake8.

129

130

Args:

131

e: Error namedtuple instance

132

133

Returns:

134

Adapted error tuple

135

"""

136

137

@staticmethod

138

def add_options(optmanager) -> None:

139

"""

140

Register configuration options with flake8.

141

142

Args:

143

optmanager: flake8 option manager instance

144

"""

145

146

def should_warn(self, code: str) -> bool:

147

"""

148

Check if warning code should be reported.

149

150

Args:

151

code: Error code (e.g., "B001")

152

153

Returns:

154

True if warning should be reported

155

"""

156

```

157

158

### Error Detection Rules

159

160

flake8-bugbear implements over 70 specific error detection rules. Each rule has a unique code (B001-B950) and targets specific code patterns that are likely bugs or poor design choices.

161

162

#### Logic and Exception Handling Errors (B001-B030)

163

164

```python { .api }

165

# B001: Bare except clauses

166

try:

167

risky_operation()

168

except: # Error: Use "except Exception:" instead

169

pass

170

171

# B002: Unary prefix increment

172

++n # Error: Python doesn't support this, use n += 1

173

174

# B003: os.environ assignment

175

os.environ = {} # Error: Use os.environ.clear() instead

176

177

# B004: hasattr() with __call__

178

hasattr(obj, '__call__') # Error: Use callable(obj) instead

179

180

# B005: strip() with multi-character strings

181

text.strip('abc') # Error: Misleading, use replace() or regex

182

183

# B006: Mutable default arguments

184

def func(items=[]): # Error: Use items=None, then items = items or []

185

pass

186

187

# B007: Unused loop variables

188

for i in range(10): # Error: If unused, name it _i

189

print("hello")

190

191

# B008: Function calls in argument defaults

192

def func(timestamp=time.time()): # Error: Called once at definition

193

pass

194

195

# B009-B010: getattr/setattr with known attributes

196

getattr(obj, 'attr') # Error: Use obj.attr directly

197

setattr(obj, 'attr', val) # Error: Use obj.attr = val

198

199

# B011: assert False

200

assert False # Error: Use raise AssertionError() instead

201

202

# B012: Control flow in finally blocks

203

try:

204

operation()

205

finally:

206

return value # Error: Will silence exceptions

207

208

# B013-B014: Exception handling issues

209

except (ValueError,): # B013: Redundant tuple

210

except (ValueError, ValueError): # B014: Duplicate exception types

211

```

212

213

#### Language Feature Misuse (B015-B030)

214

215

```python { .api }

216

# B015: Redundant comparisons

217

if x == True: # Error: Use if x: instead

218

219

# B016: Raising in finally

220

try:

221

operation()

222

finally:

223

raise Exception() # Error: Will override original exception

224

225

# B017: pytest.raises without match

226

with pytest.raises(ValueError): # Error: Add match= parameter

227

risky_operation()

228

229

# B018: Useless expressions

230

x + 1 # Error: Statement has no effect

231

232

# B019: lru_cache without parameters

233

@lru_cache # Error: Use @lru_cache() with parentheses

234

235

# B020: Loop variable usage outside loop

236

for i in range(10):

237

items.append(i)

238

print(i) # Error: i is not guaranteed to be defined

239

240

# B021: f-string in format()

241

"{}".format(f"{x}") # Error: Use f-string directly

242

243

# B022: Useless contextlib.suppress

244

with contextlib.suppress(): # Error: No exception types specified

245

operation()

246

247

# B023: Function definition scope issues

248

for i in range(3):

249

def func():

250

return i # Error: Will always return final loop value

251

252

# B024: Abstract methods without @abstractmethod

253

class Base:

254

def method(self):

255

raise NotImplementedError() # Error: Use @abstractmethod

256

257

# B025: Duplicate except handlers

258

try:

259

operation()

260

except ValueError:

261

handle_error()

262

except ValueError: # Error: Duplicate handler

263

handle_error()

264

265

# B026: Star-arg unpacking after keyword argument

266

def func(a, b, **kwargs, *args): # Error: *args after **kwargs is discouraged

267

pass

268

269

# B027: Empty abstract method without decorator

270

class Base:

271

def method(self): # Error: Empty method should use @abstractmethod

272

pass

273

274

# B028: warnings.warn without stacklevel

275

import warnings

276

warnings.warn("message") # Error: Should specify stacklevel=2

277

278

# B029: Empty except tuple

279

try:

280

operation()

281

except (): # Error: Empty tuple catches nothing

282

pass

283

```

284

285

#### Advanced Pattern Detection (B030-B041)

286

287

```python { .api }

288

# B030: Except handler names

289

except ValueError as e: # OK

290

except ValueError as ValueError: # Error: Shadowing exception class

291

292

# B031: itertools.islice with generators

293

list(itertools.islice(generator, 10)) # Error: Consider using itertools.takewhile

294

295

# B032: Possible unintentional type annotations

296

def func():

297

x: int # Error: Missing assignment, use x: int = 0

298

299

# B033: Duplicate set elements

300

{1, 2, 1} # Error: Duplicate element

301

302

# B034: re.sub without flags

303

re.sub(r'pattern', 'replacement', text) # Error: Consider adding flags

304

305

# B035: Static key in dict comprehension

306

{key: value for item in items} # Error: key doesn't depend on item

307

308

# B036: Found f-string with incorrect prefix

309

rf"regex {pattern}" # Error: Use fr"regex {pattern}" instead

310

311

# B037: __exit__ return value

312

def __exit__(self, exc_type, exc_val, exc_tb):

313

return True # Error: Usually should return None

314

315

# B039: contextlib.suppress with BaseException

316

with contextlib.suppress(BaseException): # Error: Too broad

317

operation()

318

319

# B040: Exception caught without being used

320

try:

321

operation()

322

except ValueError as e: # Error: e is never used

323

log("Error occurred")

324

325

# B041: Repeated key-value pairs in dict literals

326

{"key": 1, "key": 2} # Error: Duplicate key

327

```

328

329

#### Class and Method Design (B901-B911)

330

331

```python { .api }

332

# B901: return in generator

333

def generator():

334

yield 1

335

return 2 # Error: Use return without value

336

337

# B902: Invalid first argument names

338

class Example:

339

def method(bad_self): # Error: Should be 'self'

340

pass

341

342

@classmethod

343

def classmethod(bad_cls): # Error: Should be 'cls'

344

pass

345

346

# B903: Data class without slots

347

@dataclass

348

class Example: # Error: Consider @dataclass(slots=True) for performance

349

value: int

350

351

# B904: re-raise without from

352

try:

353

operation()

354

except ValueError:

355

raise RuntimeError("Failed") # Error: Use "raise ... from e"

356

357

# B905: zip() without strict parameter

358

zip(list1, list2) # Error: Use zip(list1, list2, strict=True)

359

360

# B906: visit_ methods without isinstance check

361

def visit_Name(self, node): # Error: Add isinstance(node, ast.Name) check

362

pass

363

364

# B907: JSON loads without secure defaults

365

json.loads(data) # Error: Consider security implications

366

367

# B908: Context manager protocols

368

with context_manager() as ctx:

369

pass # Various context manager usage patterns

370

371

# B909: Mutation during iteration

372

for item in items:

373

items.remove(item) # Error: Modifying collection during iteration

374

375

# B910: Counter() instead of defaultdict(int)

376

defaultdict(int) # Error: Use Counter() for better memory efficiency

377

378

# B911: itertools.batched() without explicit strict parameter

379

itertools.batched(data, 3) # Error: Add strict=True parameter

380

```

381

382

#### Line Length and Formatting (B950)

383

384

```python { .api }

385

# B950: Line too long

386

very_long_line = "This line exceeds the configured maximum length and will trigger B950" # Error if > max_line_length

387

```

388

389

### Version Information

390

391

Access to package version information:

392

393

```python { .api }

394

__version__: str # Package version string (e.g., "24.12.12")

395

```

396

397

### AST Visitor Implementation

398

399

The BugBearVisitor class handles the actual AST traversal and error detection:

400

401

```python { .api }

402

class BugBearVisitor:

403

"""

404

AST visitor that traverses code and detects rule violations.

405

406

Attributes:

407

filename (str): Name of file being analyzed

408

lines (List[str]): Source code lines

409

b008_b039_extend_immutable_calls (set): Extended immutable calls configuration

410

b902_classmethod_decorators (set): Classmethod decorators configuration

411

node_window (list): Recent AST nodes for context

412

errors (list): Collected error instances

413

contexts (list): Current context stack

414

b040_caught_exception: Exception context for B040 rule

415

"""

416

417

def visit(self, node):

418

"""Visit an AST node and apply bugbear rules."""

419

420

def generic_visit(self, node):

421

"""Generic visit implementation for unhandled node types."""

422

```

423

424

### Utility Functions

425

426

Public utility functions for AST analysis:

427

428

```python { .api }

429

def compose_call_path(node):

430

"""

431

Compose call path from AST call node.

432

433

Args:

434

node: AST node (Call, Attribute, or Name)

435

436

Yields:

437

str: Components of the call path

438

439

Example:

440

For `foo.bar.baz()`, yields: "foo", "bar", "baz"

441

"""

442

443

def is_name(node: ast.expr, name: str) -> bool:

444

"""

445

Check if AST node represents a specific name.

446

447

Args:

448

node: AST expression node to check

449

name: Name to match (supports dotted names like "typing.Generator")

450

451

Returns:

452

bool: True if node matches the given name

453

454

Example:

455

is_name(node, "typing.Generator") matches typing.Generator references

456

"""

457

```

458

459

## Error Message Format

460

461

All errors follow a consistent format:

462

463

```

464

{filename}:{line}:{column} {code} {message}

465

```

466

467

Examples:

468

```

469

example.py:5:8 B001 Do not use bare `except:`, it also catches unexpected events

470

example.py:12:4 B006 Do not use mutable data structures for argument defaults

471

example.py:20:1 B950 line too long (95 > 79 characters)

472

```

473

474

## Integration Examples

475

476

### With pre-commit

477

478

```yaml

479

repos:

480

- repo: https://github.com/PyCQA/flake8

481

rev: 6.0.0

482

hooks:

483

- id: flake8

484

additional_dependencies: [flake8-bugbear]

485

```

486

487

### With tox

488

489

```ini

490

[testenv:lint]

491

deps =

492

flake8

493

flake8-bugbear

494

commands = flake8 src tests

495

```

496

497

### With GitHub Actions

498

499

```yaml

500

- name: Run flake8

501

run: |

502

pip install flake8 flake8-bugbear

503

flake8 .

504

```