or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

tessl/pypi-flake8-annotations

Flake8 plugin that detects the absence of PEP 3107-style function annotations in Python code

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

To install, run

npx @tessl/cli install tessl/pypi-flake8-annotations@3.1.0

0

# flake8-annotations

1

2

A plugin for Flake8 that detects the absence of PEP 3107-style function annotations in Python code. The plugin provides comprehensive type annotation checking for functions, methods, arguments, and return types with configurable warning levels and advanced features including suppression options for None-returning functions, dummy arguments, and dynamically typed expressions.

3

4

## Package Information

5

6

- **Package Name**: flake8-annotations

7

- **Language**: Python

8

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

9

- **Plugin Integration**: Automatically registered with flake8 via entry points

10

11

## Core Imports

12

13

The package is primarily used as a flake8 plugin and does not require direct imports in most cases. For programmatic access:

14

15

```python

16

from flake8_annotations import __version__

17

from flake8_annotations.checker import TypeHintChecker, FORMATTED_ERROR

18

from flake8_annotations.error_codes import Error, ANN001, ANN201, ANN401

19

from flake8_annotations.enums import FunctionType, AnnotationType, ClassDecoratorType

20

from flake8_annotations.ast_walker import Function, Argument, FunctionVisitor

21

```

22

23

## Basic Usage

24

25

### As a Flake8 Plugin

26

27

After installation, the plugin runs automatically with flake8:

28

29

```bash

30

# Standard flake8 usage - plugin runs automatically

31

flake8 myproject/

32

33

# Enable opinionated warnings

34

flake8 --extend-select=ANN401,ANN402 myproject/

35

36

# Configure plugin options

37

flake8 --suppress-none-returning --allow-untyped-defs myproject/

38

```

39

40

### Configuration Example

41

42

```ini

43

# setup.cfg or tox.ini

44

[flake8]

45

extend-select = ANN401,ANN402

46

suppress-none-returning = True

47

suppress-dummy-args = True

48

allow-untyped-nested = True

49

mypy-init-return = True

50

dispatch-decorators = singledispatch,singledispatchmethod,custom_dispatch

51

overload-decorators = overload,custom_overload

52

allow-star-arg-any = True

53

respect-type-ignore = True

54

```

55

56

### Programmatic Usage

57

58

```python

59

import ast

60

from flake8_annotations.checker import TypeHintChecker

61

62

# Parse source code

63

source_code = '''

64

def add_numbers(a, b):

65

return a + b

66

'''

67

68

lines = source_code.splitlines(keepends=True)

69

tree = ast.parse(source_code)

70

71

# Create checker instance

72

checker = TypeHintChecker(tree, lines)

73

74

# Configure options (normally done by flake8)

75

checker.suppress_none_returning = False

76

checker.suppress_dummy_args = False

77

checker.allow_untyped_defs = False

78

checker.allow_untyped_nested = False

79

checker.mypy_init_return = False

80

checker.allow_star_arg_any = False

81

checker.respect_type_ignore = False

82

checker.dispatch_decorators = {"singledispatch", "singledispatchmethod"}

83

checker.overload_decorators = {"overload"}

84

85

# Run checks and collect errors

86

errors = list(checker.run())

87

for error in errors:

88

line, col, message, checker_type = error

89

print(f"{line}:{col} {message}")

90

```

91

92

## Architecture

93

94

The plugin follows a modular design with clear separation of concerns and a sophisticated AST analysis pipeline:

95

96

### **Core Components:**

97

98

- **TypeHintChecker**: Main flake8 plugin class that orchestrates the checking process and manages configuration

99

- **AST Walker**: Parses and analyzes function definitions using Python's AST module with context-aware visiting

100

- **Error Classification**: Maps missing annotations to specific error codes based on function context, visibility, and argument types

101

- **Configuration**: Extensive configuration options for customizing behavior with decorator pattern support

102

103

### **AST Analysis Pipeline:**

104

105

1. **Source Parsing**: Converts source code lines into AST tree with type comments enabled

106

2. **Context-Aware Traversal**: `FunctionVisitor` uses context switching to track nested functions, class methods, and decorators

107

3. **Function Analysis**: Each function node is converted to a `Function` object with complete metadata including visibility, decorators, and argument details

108

4. **Return Analysis**: `ReturnVisitor` analyzes return statements to determine if functions only return `None`

109

5. **Annotation Detection**: Identifies missing type annotations and dynamically typed expressions (typing.Any)

110

6. **Error Classification**: Uses cached helper functions to map missing annotations to specific error codes based on context

111

7. **Filtering**: Applies configuration-based filtering for dispatch decorators, overload patterns, and type ignore comments

112

113

### **Advanced Features:**

114

115

- **Overload Pattern Support**: Implements proper `typing.overload` decorator handling per typing documentation

116

- **Dispatch Decorator Recognition**: Configurable support for `functools.singledispatch` and similar patterns

117

- **Type Ignore Respect**: Honors `# type: ignore` comments at function and module levels

118

- **Dynamic Typing Detection**: Identifies and optionally suppresses `typing.Any` usage warnings

119

120

The checker integrates seamlessly with flake8's plugin system, leveraging Python's AST module for comprehensive source code analysis while maintaining high performance through caching and efficient context tracking.

121

122

## Capabilities

123

124

### Package Constants

125

126

Core package constants and type definitions.

127

128

```python { .api }

129

__version__: str

130

# Package version string (e.g., "3.1.1")

131

132

FORMATTED_ERROR: TypeAlias = Tuple[int, int, str, Type[Any]]

133

# Type alias for flake8 error tuple format: (line_number, column_number, message, checker_type)

134

```

135

136

### Plugin Registration

137

138

The package registers itself as a flake8 plugin through setuptools entry points.

139

140

```python { .api }

141

# Entry point configuration (automatic via setuptools)

142

[tool.poetry.plugins."flake8.extension"]

143

"ANN" = "flake8_annotations.checker:TypeHintChecker"

144

```

145

146

### Main Checker Class

147

148

Core flake8 plugin implementation that performs type annotation checking.

149

150

```python { .api }

151

class TypeHintChecker:

152

"""Top level checker for linting the presence of type hints in function definitions."""

153

154

name: str # "flake8-annotations"

155

version: str # Plugin version

156

157

def __init__(self, tree: Optional[ast.Module], lines: List[str]):

158

"""

159

Initialize checker with AST tree and source lines.

160

161

Args:

162

tree: AST tree (required by flake8 but not used)

163

lines: Source code lines for analysis

164

"""

165

166

def run(self) -> Generator[Tuple[int, int, str, Type[Any]], None, None]:

167

"""

168

Perform type annotation checks on source code.

169

170

Yields:

171

Tuples of (line_number, column_number, message, checker_type)

172

"""

173

174

@classmethod

175

def add_options(cls, parser: OptionManager) -> None:

176

"""Add custom configuration options to flake8."""

177

178

@classmethod

179

def parse_options(cls, options: Namespace) -> None:

180

"""Parse custom configuration options from flake8."""

181

```

182

183

**Configuration Attributes** (set by flake8):

184

185

```python { .api }

186

# Boolean configuration options

187

suppress_none_returning: bool # Skip errors for None-returning functions

188

suppress_dummy_args: bool # Skip errors for dummy arguments named '_'

189

allow_untyped_defs: bool # Skip all errors for dynamically typed functions

190

allow_untyped_nested: bool # Skip errors for dynamically typed nested functions

191

mypy_init_return: bool # Allow omission of return type hint for __init__

192

allow_star_arg_any: bool # Allow typing.Any for *args and **kwargs

193

respect_type_ignore: bool # Respect # type: ignore comments

194

195

# Set configuration options

196

dispatch_decorators: Set[str] # Decorators to treat as dispatch decorators

197

overload_decorators: Set[str] # Decorators to treat as typing.overload decorators

198

```

199

200

### Error Code Classification

201

202

Functions for mapping missing annotations to specific error codes.

203

204

```python { .api }

205

def classify_error(function: Function, arg: Argument) -> Error:

206

"""

207

Classify missing type annotation based on Function & Argument metadata.

208

209

Args:

210

function: Function object containing metadata

211

arg: Argument object with missing annotation

212

213

Returns:

214

Error object with appropriate error code

215

"""

216

```

217

218

### AST Analysis Classes

219

220

Classes for parsing and analyzing function definitions from Python AST.

221

222

```python { .api }

223

class Argument:

224

"""Represent a function argument & its metadata."""

225

226

argname: str # Name of the argument

227

lineno: int # Line number where argument is defined

228

col_offset: int # Column offset where argument is defined

229

annotation_type: AnnotationType # Type of annotation (from enums)

230

has_type_annotation: bool # Whether argument has type annotation

231

has_type_comment: bool # Whether argument has type comment

232

is_dynamically_typed: bool # Whether argument is typed as Any

233

234

@classmethod

235

def from_arg_node(cls, node: ast.arg, annotation_type_name: str) -> "Argument":

236

"""Create an Argument object from an ast.arguments node."""

237

238

def __str__(self) -> str:

239

"""String representation of argument."""

240

241

class Function:

242

"""Represent a function and its relevant metadata."""

243

244

name: str # Function name

245

lineno: int # Line number where function is defined

246

col_offset: int # Column offset where function is defined

247

decorator_list: List[Union[ast.Attribute, ast.Call, ast.Name]] # List of decorators

248

args: List[Argument] # List of function arguments including return

249

function_type: FunctionType # Classification of function visibility

250

is_class_method: bool # Whether function is a class method

251

class_decorator_type: Optional[ClassDecoratorType] # Type of class decorator if applicable

252

is_return_annotated: bool # Whether function has return annotation

253

has_type_comment: bool # Whether function has type comment

254

has_only_none_returns: bool # Whether function only returns None

255

is_nested: bool # Whether function is nested

256

257

def is_fully_annotated(self) -> bool:

258

"""Check that all of the function's inputs are type annotated."""

259

260

def is_dynamically_typed(self) -> bool:

261

"""Determine if the function is dynamically typed (completely lacking hints)."""

262

263

def get_missed_annotations(self) -> List[Argument]:

264

"""Provide a list of arguments with missing type annotations."""

265

266

def get_annotated_arguments(self) -> List[Argument]:

267

"""Provide a list of arguments with type annotations."""

268

269

def has_decorator(self, check_decorators: Set[str]) -> bool:

270

"""

271

Determine whether function is decorated by any of the provided decorators.

272

273

Args:

274

check_decorators: Set of decorator names to check for

275

276

Returns:

277

True if function has any of the specified decorators

278

"""

279

280

@classmethod

281

def from_function_node(

282

cls,

283

node: Union[ast.FunctionDef, ast.AsyncFunctionDef],

284

lines: List[str],

285

**kwargs: Any

286

) -> "Function":

287

"""Create a Function object from ast.FunctionDef or ast.AsyncFunctionDef nodes."""

288

289

@staticmethod

290

def get_function_type(function_name: str) -> FunctionType:

291

"""Determine the function's FunctionType from its name."""

292

293

@staticmethod

294

def get_class_decorator_type(

295

function_node: Union[ast.FunctionDef, ast.AsyncFunctionDef]

296

) -> Optional[ClassDecoratorType]:

297

"""Get the class method's decorator type from its function node."""

298

299

@staticmethod

300

def colon_seeker(node: Union[ast.FunctionDef, ast.AsyncFunctionDef], lines: List[str]) -> Tuple[int, int]:

301

"""

302

Find the line & column indices of the function definition's closing colon.

303

304

Args:

305

node: AST function node to analyze

306

lines: Source code lines

307

308

Returns:

309

Tuple of (line_number, column_offset) for the closing colon

310

"""

311

312

class FunctionVisitor(ast.NodeVisitor):

313

"""AST visitor for walking and describing all contained functions."""

314

315

lines: List[str] # Source code lines

316

function_definitions: List[Function] # Collected function definitions

317

318

def __init__(self, lines: List[str]):

319

"""Initialize visitor with source lines."""

320

321

def switch_context(self, node: Union[ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef]) -> None:

322

"""Context-aware node visitor for tracking function context."""

323

324

**Module Type Aliases and Constants:**

325

326

```python { .api }

327

AST_DECORATOR_NODES: TypeAlias = Union[ast.Attribute, ast.Call, ast.Name]

328

AST_DEF_NODES: TypeAlias = Union[ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef]

329

AST_FUNCTION_TYPES: TypeAlias = Union[ast.FunctionDef, ast.AsyncFunctionDef]

330

AST_ARG_TYPES: Tuple[str, ...] = ("posonlyargs", "args", "vararg", "kwonlyargs", "kwarg")

331

```

332

333

class ReturnVisitor(ast.NodeVisitor):

334

"""Specialized AST visitor for visiting return statements of a function node."""

335

336

def __init__(self, parent_node: Union[ast.FunctionDef, ast.AsyncFunctionDef]):

337

"""Initialize with parent function node."""

338

339

@property

340

def has_only_none_returns(self) -> bool:

341

"""Return True if the parent node only returns None or has no returns."""

342

343

def visit_Return(self, node: ast.Return) -> None:

344

"""Check each Return node to see if it returns anything other than None."""

345

```

346

347

### Error Code Classes

348

349

All error codes inherit from a base Error class and represent specific type annotation violations.

350

351

```python { .api }

352

class Error:

353

"""Base class for linting error codes & relevant metadata."""

354

355

argname: str # Argument name where error occurred

356

lineno: int # Line number of error

357

col_offset: int # Column offset of error

358

359

def __init__(self, message: str):

360

"""Initialize with error message template."""

361

362

@classmethod

363

def from_argument(cls, argument: Argument) -> "Error":

364

"""Set error metadata from the input Argument object."""

365

366

@classmethod

367

def from_function(cls, function: Function) -> "Error":

368

"""Set error metadata from the input Function object."""

369

370

def to_flake8(self) -> Tuple[int, int, str, Type[Any]]:

371

"""Format the Error into flake8-expected tuple format."""

372

```

373

374

**Function Argument Error Codes:**

375

376

```python { .api }

377

class ANN001(Error):

378

"""Missing type annotation for function argument"""

379

def __init__(self, argname: str, lineno: int, col_offset: int): ...

380

def to_flake8(self) -> Tuple[int, int, str, Type[Any]]:

381

"""Custom formatter that includes argument name in message."""

382

383

class ANN002(Error):

384

"""Missing type annotation for *args"""

385

def __init__(self, argname: str, lineno: int, col_offset: int): ...

386

def to_flake8(self) -> Tuple[int, int, str, Type[Any]]:

387

"""Custom formatter that includes argument name in message."""

388

389

class ANN003(Error):

390

"""Missing type annotation for **kwargs"""

391

def __init__(self, argname: str, lineno: int, col_offset: int): ...

392

def to_flake8(self) -> Tuple[int, int, str, Type[Any]]:

393

"""Custom formatter that includes argument name in message."""

394

```

395

396

**Method Argument Error Codes:**

397

398

```python { .api }

399

class ANN101(Error):

400

"""Missing type annotation for self in method"""

401

def __init__(self, argname: str, lineno: int, col_offset: int): ...

402

403

class ANN102(Error):

404

"""Missing type annotation for cls in classmethod"""

405

def __init__(self, argname: str, lineno: int, col_offset: int): ...

406

```

407

408

**Return Type Error Codes:**

409

410

```python { .api }

411

class ANN201(Error):

412

"""Missing return type annotation for public function"""

413

def __init__(self, argname: str, lineno: int, col_offset: int): ...

414

415

class ANN202(Error):

416

"""Missing return type annotation for protected function"""

417

def __init__(self, argname: str, lineno: int, col_offset: int): ...

418

419

class ANN203(Error):

420

"""Missing return type annotation for secret function"""

421

def __init__(self, argname: str, lineno: int, col_offset: int): ...

422

423

class ANN204(Error):

424

"""Missing return type annotation for special method"""

425

def __init__(self, argname: str, lineno: int, col_offset: int): ...

426

427

class ANN205(Error):

428

"""Missing return type annotation for staticmethod"""

429

def __init__(self, argname: str, lineno: int, col_offset: int): ...

430

431

class ANN206(Error):

432

"""Missing return type annotation for classmethod"""

433

def __init__(self, argname: str, lineno: int, col_offset: int): ...

434

```

435

436

**Opinionated Warning Error Codes:**

437

438

```python { .api }

439

class ANN401(Error):

440

"""Dynamically typed expressions (typing.Any) are disallowed"""

441

def __init__(self, argname: str, lineno: int, col_offset: int): ...

442

443

class ANN402(Error):

444

"""Type comments are disallowed"""

445

def __init__(self, argname: str, lineno: int, col_offset: int): ...

446

```

447

448

### Enumeration Types

449

450

Enumerations for categorizing functions and annotations.

451

452

```python { .api }

453

class FunctionType(Enum):

454

"""Represent Python's function types."""

455

PUBLIC = auto() # Regular public functions

456

PROTECTED = auto() # Functions with single underscore prefix

457

PRIVATE = auto() # Functions with double underscore prefix

458

SPECIAL = auto() # Functions with double underscore prefix and suffix

459

460

class ClassDecoratorType(Enum):

461

"""Represent Python's built-in class method decorators."""

462

CLASSMETHOD = auto() # @classmethod decorator

463

STATICMETHOD = auto() # @staticmethod decorator

464

465

class AnnotationType(Enum):

466

"""Represent the kind of missing type annotation."""

467

POSONLYARGS = auto() # Positional-only arguments

468

ARGS = auto() # Regular arguments

469

VARARG = auto() # *args

470

KWONLYARGS = auto() # Keyword-only arguments

471

KWARG = auto() # **kwargs

472

RETURN = auto() # Return type

473

```

474

475

## Configuration Options

476

477

The plugin provides extensive configuration options to customize its behavior:

478

479

### Suppression Options

480

481

```python { .api }

482

--suppress-none-returning: bool

483

# Suppress ANN200-level errors for functions with only None returns

484

# Default: False

485

486

--suppress-dummy-args: bool

487

# Suppress ANN000-level errors for dummy arguments named '_'

488

# Default: False

489

490

--allow-untyped-defs: bool

491

# Suppress all errors for dynamically typed functions

492

# Default: False

493

494

--allow-untyped-nested: bool

495

# Suppress all errors for dynamically typed nested functions

496

# Default: False

497

498

--mypy-init-return: bool

499

# Allow omission of return type hint for __init__ if at least one argument is annotated

500

# Default: False

501

502

--allow-star-arg-any: bool

503

# Suppress ANN401 for dynamically typed *args and **kwargs

504

# Default: False

505

506

--respect-type-ignore: bool

507

# Suppress errors for functions with '# type: ignore' comments

508

# Default: False

509

```

510

511

### Decorator Configuration

512

513

```python { .api }

514

--dispatch-decorators: List[str]

515

# Comma-separated list of decorators to treat as dispatch decorators

516

# Functions with these decorators skip annotation checks

517

# Default: ["singledispatch", "singledispatchmethod"]

518

519

--overload-decorators: List[str]

520

# Comma-separated list of decorators to treat as typing.overload decorators

521

# Implements overload pattern handling per typing documentation

522

# Default: ["overload"]

523

```

524

525

## Error Code Reference

526

527

The plugin provides 14 distinct error codes organized by category:

528

529

**Function Arguments (ANN001-ANN003):**

530

- ANN001: Missing type annotation for function argument

531

- ANN002: Missing type annotation for *args

532

- ANN003: Missing type annotation for **kwargs

533

534

**Method Arguments (ANN101-ANN102):**

535

- ANN101: Missing type annotation for self in method

536

- ANN102: Missing type annotation for cls in classmethod

537

538

**Return Types (ANN201-ANN206):**

539

- ANN201: Missing return type annotation for public function

540

- ANN202: Missing return type annotation for protected function

541

- ANN203: Missing return type annotation for secret function

542

- ANN204: Missing return type annotation for special method

543

- ANN205: Missing return type annotation for staticmethod

544

- ANN206: Missing return type annotation for classmethod

545

546

**Opinionated Warnings (ANN401-ANN402, disabled by default):**

547

- ANN401: Dynamically typed expressions (typing.Any) are disallowed

548

- ANN402: Type comments are disallowed

549

550

## Advanced Features

551

552

### Generic Function Support

553

554

The plugin automatically skips annotation checks for functions decorated with dispatch decorators (configurable via `--dispatch-decorators`), supporting patterns like `functools.singledispatch`.

555

556

### Overload Pattern Support

557

558

Implements proper handling of `typing.overload` decorator patterns where a series of overload-decorated definitions must be followed by exactly one non-overload-decorated definition.

559

560

### Type Ignore Support

561

562

When `--respect-type-ignore` is enabled, the plugin respects `# type: ignore` comments at both function and module levels, including mypy-style ignore patterns.

563

564

### Dynamic Typing Detection

565

566

The plugin can detect and optionally suppress warnings for dynamically typed expressions (typing.Any) with configurable patterns for different annotation contexts.