Flake8 plugin that detects the absence of PEP 3107-style function annotations in Python code
npx @tessl/cli install tessl/pypi-flake8-annotations@3.1.00
# 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.