or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

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

index.mddocs/

flake8-annotations

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.

Package Information

  • Package Name: flake8-annotations
  • Language: Python
  • Installation: pip install flake8-annotations
  • Plugin Integration: Automatically registered with flake8 via entry points

Core Imports

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

from flake8_annotations import __version__
from flake8_annotations.checker import TypeHintChecker, FORMATTED_ERROR
from flake8_annotations.error_codes import Error, ANN001, ANN201, ANN401
from flake8_annotations.enums import FunctionType, AnnotationType, ClassDecoratorType
from flake8_annotations.ast_walker import Function, Argument, FunctionVisitor

Basic Usage

As a Flake8 Plugin

After installation, the plugin runs automatically with flake8:

# Standard flake8 usage - plugin runs automatically
flake8 myproject/

# Enable opinionated warnings
flake8 --extend-select=ANN401,ANN402 myproject/

# Configure plugin options
flake8 --suppress-none-returning --allow-untyped-defs myproject/

Configuration Example

# setup.cfg or tox.ini
[flake8]
extend-select = ANN401,ANN402
suppress-none-returning = True
suppress-dummy-args = True
allow-untyped-nested = True
mypy-init-return = True
dispatch-decorators = singledispatch,singledispatchmethod,custom_dispatch
overload-decorators = overload,custom_overload
allow-star-arg-any = True
respect-type-ignore = True

Programmatic Usage

import ast
from flake8_annotations.checker import TypeHintChecker

# Parse source code
source_code = '''
def add_numbers(a, b):
    return a + b
'''

lines = source_code.splitlines(keepends=True)
tree = ast.parse(source_code)

# Create checker instance
checker = TypeHintChecker(tree, lines)

# Configure options (normally done by flake8)
checker.suppress_none_returning = False
checker.suppress_dummy_args = False
checker.allow_untyped_defs = False
checker.allow_untyped_nested = False
checker.mypy_init_return = False
checker.allow_star_arg_any = False
checker.respect_type_ignore = False
checker.dispatch_decorators = {"singledispatch", "singledispatchmethod"}
checker.overload_decorators = {"overload"}

# Run checks and collect errors
errors = list(checker.run())
for error in errors:
    line, col, message, checker_type = error
    print(f"{line}:{col} {message}")

Architecture

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

Core Components:

  • TypeHintChecker: Main flake8 plugin class that orchestrates the checking process and manages configuration
  • AST Walker: Parses and analyzes function definitions using Python's AST module with context-aware visiting
  • Error Classification: Maps missing annotations to specific error codes based on function context, visibility, and argument types
  • Configuration: Extensive configuration options for customizing behavior with decorator pattern support

AST Analysis Pipeline:

  1. Source Parsing: Converts source code lines into AST tree with type comments enabled
  2. Context-Aware Traversal: FunctionVisitor uses context switching to track nested functions, class methods, and decorators
  3. Function Analysis: Each function node is converted to a Function object with complete metadata including visibility, decorators, and argument details
  4. Return Analysis: ReturnVisitor analyzes return statements to determine if functions only return None
  5. Annotation Detection: Identifies missing type annotations and dynamically typed expressions (typing.Any)
  6. Error Classification: Uses cached helper functions to map missing annotations to specific error codes based on context
  7. Filtering: Applies configuration-based filtering for dispatch decorators, overload patterns, and type ignore comments

Advanced Features:

  • Overload Pattern Support: Implements proper typing.overload decorator handling per typing documentation
  • Dispatch Decorator Recognition: Configurable support for functools.singledispatch and similar patterns
  • Type Ignore Respect: Honors # type: ignore comments at function and module levels
  • Dynamic Typing Detection: Identifies and optionally suppresses typing.Any usage warnings

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.

Capabilities

Package Constants

Core package constants and type definitions.

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

FORMATTED_ERROR: TypeAlias = Tuple[int, int, str, Type[Any]]
    # Type alias for flake8 error tuple format: (line_number, column_number, message, checker_type)

Plugin Registration

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

# Entry point configuration (automatic via setuptools)
[tool.poetry.plugins."flake8.extension"]
"ANN" = "flake8_annotations.checker:TypeHintChecker"

Main Checker Class

Core flake8 plugin implementation that performs type annotation checking.

class TypeHintChecker:
    """Top level checker for linting the presence of type hints in function definitions."""
    
    name: str  # "flake8-annotations"
    version: str  # Plugin version
    
    def __init__(self, tree: Optional[ast.Module], lines: List[str]):
        """
        Initialize checker with AST tree and source lines.
        
        Args:
            tree: AST tree (required by flake8 but not used)
            lines: Source code lines for analysis
        """
    
    def run(self) -> Generator[Tuple[int, int, str, Type[Any]], None, None]:
        """
        Perform type annotation checks on source code.
        
        Yields:
            Tuples of (line_number, column_number, message, checker_type)
        """
    
    @classmethod
    def add_options(cls, parser: OptionManager) -> None:
        """Add custom configuration options to flake8."""
    
    @classmethod
    def parse_options(cls, options: Namespace) -> None:
        """Parse custom configuration options from flake8."""

Configuration Attributes (set by flake8):

# Boolean configuration options
suppress_none_returning: bool  # Skip errors for None-returning functions
suppress_dummy_args: bool      # Skip errors for dummy arguments named '_'
allow_untyped_defs: bool       # Skip all errors for dynamically typed functions
allow_untyped_nested: bool     # Skip errors for dynamically typed nested functions
mypy_init_return: bool         # Allow omission of return type hint for __init__
allow_star_arg_any: bool       # Allow typing.Any for *args and **kwargs
respect_type_ignore: bool      # Respect # type: ignore comments

# Set configuration options
dispatch_decorators: Set[str]  # Decorators to treat as dispatch decorators
overload_decorators: Set[str]  # Decorators to treat as typing.overload decorators

Error Code Classification

Functions for mapping missing annotations to specific error codes.

def classify_error(function: Function, arg: Argument) -> Error:
    """
    Classify missing type annotation based on Function & Argument metadata.
    
    Args:
        function: Function object containing metadata
        arg: Argument object with missing annotation
        
    Returns:
        Error object with appropriate error code
    """

AST Analysis Classes

Classes for parsing and analyzing function definitions from Python AST.

class Argument:
    """Represent a function argument & its metadata."""
    
    argname: str                    # Name of the argument
    lineno: int                     # Line number where argument is defined
    col_offset: int                 # Column offset where argument is defined
    annotation_type: AnnotationType # Type of annotation (from enums)
    has_type_annotation: bool       # Whether argument has type annotation
    has_type_comment: bool          # Whether argument has type comment
    is_dynamically_typed: bool      # Whether argument is typed as Any
    
    @classmethod
    def from_arg_node(cls, node: ast.arg, annotation_type_name: str) -> "Argument":
        """Create an Argument object from an ast.arguments node."""
    
    def __str__(self) -> str:
        """String representation of argument."""

class Function:
    """Represent a function and its relevant metadata."""
    
    name: str                                          # Function name
    lineno: int                                        # Line number where function is defined
    col_offset: int                                    # Column offset where function is defined
    decorator_list: List[Union[ast.Attribute, ast.Call, ast.Name]]  # List of decorators
    args: List[Argument]                               # List of function arguments including return
    function_type: FunctionType                        # Classification of function visibility
    is_class_method: bool                              # Whether function is a class method
    class_decorator_type: Optional[ClassDecoratorType] # Type of class decorator if applicable
    is_return_annotated: bool                          # Whether function has return annotation
    has_type_comment: bool                             # Whether function has type comment
    has_only_none_returns: bool                        # Whether function only returns None
    is_nested: bool                                    # Whether function is nested
    
    def is_fully_annotated(self) -> bool:
        """Check that all of the function's inputs are type annotated."""
    
    def is_dynamically_typed(self) -> bool:
        """Determine if the function is dynamically typed (completely lacking hints)."""
    
    def get_missed_annotations(self) -> List[Argument]:
        """Provide a list of arguments with missing type annotations."""
    
    def get_annotated_arguments(self) -> List[Argument]:
        """Provide a list of arguments with type annotations."""
    
    def has_decorator(self, check_decorators: Set[str]) -> bool:
        """
        Determine whether function is decorated by any of the provided decorators.
        
        Args:
            check_decorators: Set of decorator names to check for
            
        Returns:
            True if function has any of the specified decorators
        """
    
    @classmethod
    def from_function_node(
        cls, 
        node: Union[ast.FunctionDef, ast.AsyncFunctionDef], 
        lines: List[str], 
        **kwargs: Any
    ) -> "Function":
        """Create a Function object from ast.FunctionDef or ast.AsyncFunctionDef nodes."""
    
    @staticmethod
    def get_function_type(function_name: str) -> FunctionType:
        """Determine the function's FunctionType from its name."""
    
    @staticmethod
    def get_class_decorator_type(
        function_node: Union[ast.FunctionDef, ast.AsyncFunctionDef]
    ) -> Optional[ClassDecoratorType]:
        """Get the class method's decorator type from its function node."""
    
    @staticmethod
    def colon_seeker(node: Union[ast.FunctionDef, ast.AsyncFunctionDef], lines: List[str]) -> Tuple[int, int]:
        """
        Find the line & column indices of the function definition's closing colon.
        
        Args:
            node: AST function node to analyze
            lines: Source code lines
            
        Returns:
            Tuple of (line_number, column_offset) for the closing colon
        """

class FunctionVisitor(ast.NodeVisitor):
    """AST visitor for walking and describing all contained functions."""
    
    lines: List[str]                    # Source code lines
    function_definitions: List[Function] # Collected function definitions
    
    def __init__(self, lines: List[str]):
        """Initialize visitor with source lines."""
    
    def switch_context(self, node: Union[ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef]) -> None:
        """Context-aware node visitor for tracking function context."""

**Module Type Aliases and Constants:**

```python { .api }
AST_DECORATOR_NODES: TypeAlias = Union[ast.Attribute, ast.Call, ast.Name]
AST_DEF_NODES: TypeAlias = Union[ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef]
AST_FUNCTION_TYPES: TypeAlias = Union[ast.FunctionDef, ast.AsyncFunctionDef]
AST_ARG_TYPES: Tuple[str, ...] = ("posonlyargs", "args", "vararg", "kwonlyargs", "kwarg")

class ReturnVisitor(ast.NodeVisitor): """Specialized AST visitor for visiting return statements of a function node."""

def __init__(self, parent_node: Union[ast.FunctionDef, ast.AsyncFunctionDef]):
    """Initialize with parent function node."""

@property
def has_only_none_returns(self) -> bool:
    """Return True if the parent node only returns None or has no returns."""

def visit_Return(self, node: ast.Return) -> None:
    """Check each Return node to see if it returns anything other than None."""
### Error Code Classes

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

```python { .api }
class Error:
    """Base class for linting error codes & relevant metadata."""
    
    argname: str     # Argument name where error occurred
    lineno: int      # Line number of error
    col_offset: int  # Column offset of error
    
    def __init__(self, message: str):
        """Initialize with error message template."""
    
    @classmethod
    def from_argument(cls, argument: Argument) -> "Error":
        """Set error metadata from the input Argument object."""
    
    @classmethod
    def from_function(cls, function: Function) -> "Error":
        """Set error metadata from the input Function object."""
    
    def to_flake8(self) -> Tuple[int, int, str, Type[Any]]:
        """Format the Error into flake8-expected tuple format."""

Function Argument Error Codes:

class ANN001(Error):
    """Missing type annotation for function argument"""
    def __init__(self, argname: str, lineno: int, col_offset: int): ...
    def to_flake8(self) -> Tuple[int, int, str, Type[Any]]:
        """Custom formatter that includes argument name in message."""

class ANN002(Error):
    """Missing type annotation for *args"""
    def __init__(self, argname: str, lineno: int, col_offset: int): ...
    def to_flake8(self) -> Tuple[int, int, str, Type[Any]]:
        """Custom formatter that includes argument name in message."""

class ANN003(Error):
    """Missing type annotation for **kwargs"""
    def __init__(self, argname: str, lineno: int, col_offset: int): ...
    def to_flake8(self) -> Tuple[int, int, str, Type[Any]]:  
        """Custom formatter that includes argument name in message."""

Method Argument Error Codes:

class ANN101(Error):
    """Missing type annotation for self in method"""
    def __init__(self, argname: str, lineno: int, col_offset: int): ...

class ANN102(Error):
    """Missing type annotation for cls in classmethod"""
    def __init__(self, argname: str, lineno: int, col_offset: int): ...

Return Type Error Codes:

class ANN201(Error):
    """Missing return type annotation for public function"""
    def __init__(self, argname: str, lineno: int, col_offset: int): ...

class ANN202(Error):
    """Missing return type annotation for protected function"""
    def __init__(self, argname: str, lineno: int, col_offset: int): ...

class ANN203(Error):
    """Missing return type annotation for secret function"""
    def __init__(self, argname: str, lineno: int, col_offset: int): ...

class ANN204(Error):
    """Missing return type annotation for special method"""
    def __init__(self, argname: str, lineno: int, col_offset: int): ...

class ANN205(Error):
    """Missing return type annotation for staticmethod"""
    def __init__(self, argname: str, lineno: int, col_offset: int): ...

class ANN206(Error):
    """Missing return type annotation for classmethod"""
    def __init__(self, argname: str, lineno: int, col_offset: int): ...

Opinionated Warning Error Codes:

class ANN401(Error):
    """Dynamically typed expressions (typing.Any) are disallowed"""
    def __init__(self, argname: str, lineno: int, col_offset: int): ...

class ANN402(Error):
    """Type comments are disallowed"""
    def __init__(self, argname: str, lineno: int, col_offset: int): ...

Enumeration Types

Enumerations for categorizing functions and annotations.

class FunctionType(Enum):
    """Represent Python's function types."""
    PUBLIC = auto()     # Regular public functions
    PROTECTED = auto()  # Functions with single underscore prefix
    PRIVATE = auto()    # Functions with double underscore prefix
    SPECIAL = auto()    # Functions with double underscore prefix and suffix

class ClassDecoratorType(Enum):
    """Represent Python's built-in class method decorators."""
    CLASSMETHOD = auto()   # @classmethod decorator
    STATICMETHOD = auto()  # @staticmethod decorator

class AnnotationType(Enum):
    """Represent the kind of missing type annotation."""
    POSONLYARGS = auto()  # Positional-only arguments
    ARGS = auto()         # Regular arguments
    VARARG = auto()       # *args
    KWONLYARGS = auto()   # Keyword-only arguments
    KWARG = auto()        # **kwargs
    RETURN = auto()       # Return type

Configuration Options

The plugin provides extensive configuration options to customize its behavior:

Suppression Options

--suppress-none-returning: bool
    # Suppress ANN200-level errors for functions with only None returns
    # Default: False

--suppress-dummy-args: bool
    # Suppress ANN000-level errors for dummy arguments named '_'
    # Default: False

--allow-untyped-defs: bool
    # Suppress all errors for dynamically typed functions
    # Default: False

--allow-untyped-nested: bool
    # Suppress all errors for dynamically typed nested functions
    # Default: False

--mypy-init-return: bool
    # Allow omission of return type hint for __init__ if at least one argument is annotated
    # Default: False

--allow-star-arg-any: bool
    # Suppress ANN401 for dynamically typed *args and **kwargs
    # Default: False

--respect-type-ignore: bool
    # Suppress errors for functions with '# type: ignore' comments
    # Default: False

Decorator Configuration

--dispatch-decorators: List[str]
    # Comma-separated list of decorators to treat as dispatch decorators
    # Functions with these decorators skip annotation checks
    # Default: ["singledispatch", "singledispatchmethod"]

--overload-decorators: List[str]
    # Comma-separated list of decorators to treat as typing.overload decorators
    # Implements overload pattern handling per typing documentation
    # Default: ["overload"]

Error Code Reference

The plugin provides 14 distinct error codes organized by category:

Function Arguments (ANN001-ANN003):

  • ANN001: Missing type annotation for function argument
  • ANN002: Missing type annotation for *args
  • ANN003: Missing type annotation for **kwargs

Method Arguments (ANN101-ANN102):

  • ANN101: Missing type annotation for self in method
  • ANN102: Missing type annotation for cls in classmethod

Return Types (ANN201-ANN206):

  • ANN201: Missing return type annotation for public function
  • ANN202: Missing return type annotation for protected function
  • ANN203: Missing return type annotation for secret function
  • ANN204: Missing return type annotation for special method
  • ANN205: Missing return type annotation for staticmethod
  • ANN206: Missing return type annotation for classmethod

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

  • ANN401: Dynamically typed expressions (typing.Any) are disallowed
  • ANN402: Type comments are disallowed

Advanced Features

Generic Function Support

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

Overload Pattern Support

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.

Type Ignore Support

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

Dynamic Typing Detection

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