Flake8 plugin to validate annotations complexity

Overview

flake8-annotations-complexity

Build Status Maintainability Test Coverage PyPI - Python Version

An extension for flake8 to report on too complex type annotations.

Complex type annotations often means bad annotations usage, wrong code decomposition or improper data structure choice. They are also hard to read and make code look java-like.

Annotation complexity is maximum annotation nesting level. So List[int] complexity is 2 and Tuple[List[Optional[str]], int] is 4.

Default max annotation complexity is 3 and can be configured via --max-annotations-complexity option.

Installation

pip install flake8-annotations-complexity

Example

Sample file:

# test.py

def foo() -> List[int]:
    return [1]

Usage:

$ flake8 --max-annotations-complexity=1 test.py
test.py:4:14: TAE002 too complex annotation (2 > 1)

Contributing

We would love you to contribute to our project. It's simple:

  1. Create an issue with bug you found or proposal you have. Wait for approve from maintainer.
  2. Create a pull request. Make sure all checks are green.
  3. Fix review comments if any.
  4. Be awesome.

Here are useful tips:

Comments
  • 0.0.6 release

    0.0.6 release

    Hi, thank you for this awesome plugin! Please consider creatng a new release that contains the changes introduced in the PR #14. Otherwise, the latest release on PyPI (0.0.5) raises on Python 3.9, forcing us to resort to install from source repo.

    Привет, ребята! Спасибо за отличный плагин к flake8! К сожалению, текущая версия на PyPI (0.0.5) кидает исключение в связке с Python 3.9 - фикс уже есть в основной ветке (PR #14). Прошу выпустить новую версию 0.0.6, содержащую этот фикс - пока приходится устанавливать плагин с мастер-ветки репозитория.

    enhancement 
    opened by hoefling 10
  • Variable annotations are ignored

    Variable annotations are ignored

    Here you only check function annotations: https://github.com/best-doctor/flake8-annotations-complexity/blob/master/flake8_annotations_complexity/ast_helpres.py#L17-L20

    However, it is still possible to use really complex variable annotations:

    some_value: List[Union[List[str], Dict[str, Dict[str, str]]]] = ...
    
    opened by sobolevn 2
  • Exception with Literal[

    Exception with Literal[""]

    An exception is thrown if running flake8 and it finds an empty string in a Literal definition.

    To Reproduce I wrote the test for it, so see that: https://github.com/best-doctor/flake8-annotations-complexity/pull/11

    Output:

    Traceback (most recent call last):
      File "/home/ubuntu/.local/bin/flake8", line 8, in <module>
        sys.exit(main())
      File "/home/ubuntu/.local/lib/python3.8/site-packages/flake8/main/cli.py", line 22, in main
        app.run(argv)
      File "/home/ubuntu/.local/lib/python3.8/site-packages/flake8/main/application.py", line 360, in run
        self._run(argv)
      File "/home/ubuntu/.local/lib/python3.8/site-packages/flake8/main/application.py", line 348, in _run
        self.run_checks()
      File "/home/ubuntu/.local/lib/python3.8/site-packages/flake8/main/application.py", line 262, in run_checks
        self.file_checker_manager.run()
      File "/home/ubuntu/.local/lib/python3.8/site-packages/flake8/checker.py", line 334, in run
        self.run_serial()
      File "/home/ubuntu/.local/lib/python3.8/site-packages/flake8/checker.py", line 318, in run_serial
        checker.run_checks()
      File "/home/ubuntu/.local/lib/python3.8/site-packages/flake8/checker.py", line 598, in run_checks
        self.run_ast_checks()
      File "/home/ubuntu/.local/lib/python3.8/site-packages/flake8/checker.py", line 505, in run_ast_checks
        for (line_number, offset, text, _) in runner:
      File "/home/ubuntu/.local/lib/python3.8/site-packages/flake8_annotations_complexity/checker.py", line 23, in run
        too_difficult_annotations = validate_annotations_in_ast_node(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/flake8_annotations_complexity/ast_helpres.py", line 31, in validate_annotations_in_ast_node
        complexity = get_annotation_complexity(annotation)
      File "/home/ubuntu/.local/lib/python3.8/site-packages/flake8_annotations_complexity/ast_helpres.py", line 12, in get_annotation_complexity
        return 1 + get_annotation_complexity(annotation_node.slice.value)  # type: ignore
      File "/home/ubuntu/.local/lib/python3.8/site-packages/flake8_annotations_complexity/ast_helpres.py", line 8, in get_annotation_complexity
        annotation_node = ast.parse(annotation_node.s).body[0].value  # type: ignore
    IndexError: list index out of range
    
    bug 
    opened by Dreamsorcerer 1
  • String expression in annotations

    String expression in annotations

    According PEP 3107 we can use any expression in annotations. Annotations like this:

    def foo() -> 'String Annontation':
        pass
    

    raises syntax error due to impossibility AST parsing this string:

    Traceback (most recent call last):
      File "/Users/andrey/.envs/test/bin/flake8", line 8, in <module>
        sys.exit(main())
      File "/Users/andrey/.envs/test/lib/python3.8/site-packages/flake8/main/cli.py", line 18, in main
        app.run(argv)
      File "/Users/andrey/.envs/test/lib/python3.8/site-packages/flake8/main/application.py", line 393, in run
        self._run(argv)
      File "/Users/andrey/.envs/test/lib/python3.8/site-packages/flake8/main/application.py", line 381, in _run
        self.run_checks()
      File "/Users/andrey/.envs/test/lib/python3.8/site-packages/flake8/main/application.py", line 300, in run_checks
        self.file_checker_manager.run()
      File "/Users/andrey/.envs/test/lib/python3.8/site-packages/flake8/checker.py", line 331, in run
        self.run_serial()
      File "/Users/andrey/.envs/test/lib/python3.8/site-packages/flake8/checker.py", line 315, in run_serial
        checker.run_checks()
      File "/Users/andrey/.envs/test/lib/python3.8/site-packages/flake8/checker.py", line 598, in run_checks
        self.run_ast_checks()
      File "/Users/andrey/.envs/test/lib/python3.8/site-packages/flake8/checker.py", line 502, in run_ast_checks
        for (line_number, offset, text, check) in runner:
      File "/Users/andrey/.envs/test/lib/python3.8/site-packages/flake8_annotations_complexity/checker.py", line 23, in run
        too_difficult_annotations = validate_annotations_in_ast_node(
      File "/Users/andrey/.envs/test/lib/python3.8/site-packages/flake8_annotations_complexity/ast_helpres.py", line 28, in validate_annotations_in_ast_node
        complexity = get_annotation_compexity(annotation)
      File "/Users/andrey/.envs/test/lib/python3.8/site-packages/flake8_annotations_complexity/ast_helpres.py", line 7, in get_annotation_compexity
        annotation_node = ast.parse(annotation_node.s).body[0].value  # type: ignore
      File "/Users/andrey/.pyenv/versions/3.8.0/lib/python3.8/ast.py", line 47, in parse
        return compile(source, filename, mode, flags,
      File "<unknown>", line 1
        String Annontation
               ^
    SyntaxError: invalid syntax
    
    opened by leksuss 1
  • fails with AttributeError: 'Slice' object has no attribute 'value'

    fails with AttributeError: 'Slice' object has no attribute 'value'

      File "/Users/ilebedev/.virtualenvs/amy/bin/flake8", line 8, in <module>
        sys.exit(main())
      File "/Users/ilebedev/.virtualenvs/amy/lib/python3.8/site-packages/flake8/main/cli.py", line 18, in main
        app.run(argv)
      File "/Users/ilebedev/.virtualenvs/amy/lib/python3.8/site-packages/flake8/main/application.py", line 393, in run
        self._run(argv)
      File "/Users/ilebedev/.virtualenvs/amy/lib/python3.8/site-packages/flake8/main/application.py", line 381, in _run
        self.run_checks()
      File "/Users/ilebedev/.virtualenvs/amy/lib/python3.8/site-packages/flake8/main/application.py", line 300, in run_checks
        self.file_checker_manager.run()
      File "/Users/ilebedev/.virtualenvs/amy/lib/python3.8/site-packages/flake8/checker.py", line 331, in run
        self.run_serial()
      File "/Users/ilebedev/.virtualenvs/amy/lib/python3.8/site-packages/flake8/checker.py", line 315, in run_serial
        checker.run_checks()
      File "/Users/ilebedev/.virtualenvs/amy/lib/python3.8/site-packages/flake8/checker.py", line 598, in run_checks
        self.run_ast_checks()
      File "/Users/ilebedev/.virtualenvs/amy/lib/python3.8/site-packages/flake8/checker.py", line 502, in run_ast_checks
        for (line_number, offset, text, check) in runner:
      File "/Users/ilebedev/.virtualenvs/amy/lib/python3.8/site-packages/flake8_annotations_complexity/checker.py", line 23, in run
        too_difficult_annotations = validate_annotations_in_ast_node(
      File "/Users/ilebedev/.virtualenvs/amy/lib/python3.8/site-packages/flake8_annotations_complexity/ast_helpres.py", line 28, in validate_annotations_in_ast_node
        complexity = get_annotation_compexity(annotation)
      File "/Users/ilebedev/.virtualenvs/amy/lib/python3.8/site-packages/flake8_annotations_complexity/ast_helpres.py", line 9, in get_annotation_compexity
        return 1 + get_annotation_compexity(annotation_node.slice.value)  # type: ignore
    AttributeError: 'Slice' object has no attribute 'value'
    

    Code:

    
    PROJECTS_INFO: Final[List[Tuple[int, str, str, str]]] = [
    ]
    
    opened by Melevir 1
  • Empty tuple annotation causes crash

    Empty tuple annotation causes crash

    I have some functions that deal with unions where one variant is the empty tuple. Per https://github.com/python/mypy/issues/4211, the way to represent the empty tuple type is typing.Tuple[()]. Using this annotation crashes flake8-annotations-complexity with the following traceback:

    Traceback (most recent call last):
      File "/Users/maxchase/.pyenv/versions/structured-data/bin/flake8", line 10, in <module>
        sys.exit(main())
      File "/Users/maxchase/.pyenv/versions/3.7.4/envs/structured-data/lib/python3.7/site-packages/flake8/main/cli.py", line 18, in main
        app.run(argv)
      File "/Users/maxchase/.pyenv/versions/3.7.4/envs/structured-data/lib/python3.7/site-packages/flake8/main/application.py", line 393, in run
        self._run(argv)
      File "/Users/maxchase/.pyenv/versions/3.7.4/envs/structured-data/lib/python3.7/site-packages/flake8/main/application.py", line 381, in _run
        self.run_checks()
      File "/Users/maxchase/.pyenv/versions/3.7.4/envs/structured-data/lib/python3.7/site-packages/flake8/main/application.py", line 300, in run_checks
        self.file_checker_manager.run()
      File "/Users/maxchase/.pyenv/versions/3.7.4/envs/structured-data/lib/python3.7/site-packages/flake8/checker.py", line 331, in run
        self.run_serial()
      File "/Users/maxchase/.pyenv/versions/3.7.4/envs/structured-data/lib/python3.7/site-packages/flake8/checker.py", line 315, in run_serial
        checker.run_checks()
      File "/Users/maxchase/.pyenv/versions/3.7.4/envs/structured-data/lib/python3.7/site-packages/flake8/checker.py", line 598, in run_checks
        self.run_ast_checks()
      File "/Users/maxchase/.pyenv/versions/3.7.4/envs/structured-data/lib/python3.7/site-packages/flake8/checker.py", line 502, in run_ast_checks
        for (line_number, offset, text, check) in runner:
      File "/Users/maxchase/.pyenv/versions/3.7.4/envs/structured-data/lib/python3.7/site-packages/flake8_annotations_complexity/checker.py", line 25, in run
        self.max_annotations_complexity,
      File "/Users/maxchase/.pyenv/versions/3.7.4/envs/structured-data/lib/python3.7/site-packages/flake8_annotations_complexity/ast_helpres.py", line 28, in validate_annotations_in_ast_node
        complexity = get_annotation_compexity(annotation)
      File "/Users/maxchase/.pyenv/versions/3.7.4/envs/structured-data/lib/python3.7/site-packages/flake8_annotations_complexity/ast_helpres.py", line 9, in get_annotation_compexity
        return 1 + get_annotation_compexity(annotation_node.slice.value)  # type: ignore
      File "/Users/maxchase/.pyenv/versions/3.7.4/envs/structured-data/lib/python3.7/site-packages/flake8_annotations_complexity/ast_helpres.py", line 11, in get_annotation_compexity
        return max(get_annotation_compexity(n) for n in annotation_node.elts)
      File "/Users/maxchase/.pyenv/versions/3.7.4/envs/structured-data/lib/python3.7/site-packages/flake8_annotations_complexity/ast_helpres.py", line 11, in <genexpr>
        return max(get_annotation_compexity(n) for n in annotation_node.elts)
      File "/Users/maxchase/.pyenv/versions/3.7.4/envs/structured-data/lib/python3.7/site-packages/flake8_annotations_complexity/ast_helpres.py", line 9, in get_annotation_compexity
        return 1 + get_annotation_compexity(annotation_node.slice.value)  # type: ignore
      File "/Users/maxchase/.pyenv/versions/3.7.4/envs/structured-data/lib/python3.7/site-packages/flake8_annotations_complexity/ast_helpres.py", line 11, in get_annotation_compexity
        return max(get_annotation_compexity(n) for n in annotation_node.elts)
    ValueError: max() arg is an empty sequence
    

    I can put in a PR for this.

    opened by mwchase 1
  • `if annotation_len > 7:`

    `if annotation_len > 7:`

    annotation_len compares with constant 7 and not max_annotation_len :( But max_annotation_len is used in error message formatting.

    https://github.com/best-doctor/flake8-annotations-complexity/blob/master/flake8_annotations_complexity/ast_helpers.py#L65

    bug 
    opened by abcdenis 0
  • Add max complexity configuration from file

    Add max complexity configuration from file

    Is your feature request related to a problem? Please describe. The only documented method for configuring the max complexity is a command line switch. It is difficult to enforce a common standard in a shared codebase, or ensure CI is using the correct settings this way.

    Describe the solution you'd like A configuration option I can put in pyproject.toml to ensure the setting I desire is respected for all callers of flake8 in my repo.

    Describe alternatives you've considered Do nothing. Easiest, but it doesn't address the issue.

    Additional context N/A

    enhancement 
    opened by rpdelaney 0
Releases(v0.0.6)
Owner
BestDoctor
Check out our tech stack: https://stackshare.io/bestdoctor/bestdoctor
BestDoctor
Pylint plugin to enforce some secure coding standards for Python.

Pylint Secure Coding Standard Plugin pylint plugin that enforces some secure coding standards. Installation pip install pylint-secure-coding-standard

Nguyen Damien 2 Jan 04, 2022
Simple Python style checker in one Python file

pycodestyle (formerly called pep8) - Python style guide checker pycodestyle is a tool to check your Python code against some of the style conventions

Python Code Quality Authority 4.7k Jan 01, 2023
The strictest and most opinionated python linter ever!

wemake-python-styleguide Welcome to the strictest and most opinionated python linter ever. wemake-python-styleguide is actually a flake8 plugin with s

wemake.services 2.1k Jan 01, 2023
🦆 Better duck-typing with mypy-compatible extensions to Protocol

🦆 Quacks If it walks like a duck and it quacks like a duck, then it must be a duck Thanks to PEP544, Python now has protocols: a way to define duck t

Arie Bovenberg 9 Nov 14, 2022
A framework for detecting, highlighting and correcting grammatical errors on natural language text.

Gramformer Human and machine generated text often suffer from grammatical and/or typographical errors. It can be spelling, punctuation, grammatical or

Prithivida 1.3k Jan 08, 2023
MyPy types for WSGI applications

WSGI Types for Python This is an attempt to bring some type safety to WSGI applications using Python's new typing features (TypedDicts, Protocols). It

Blake Williams 2 Aug 18, 2021
Tool to check the completeness of MANIFEST.in for Python packages

check-manifest Are you a Python developer? Have you uploaded packages to the Python Package Index? Have you accidentally uploaded broken packages with

Marius Gedminas 270 Dec 26, 2022
:sparkles: Surface lint errors during code review

✨ Linty Fresh ✨ Keep your codebase sparkly clean with the power of LINT! Linty Fresh parses lint errors and report them back to GitHub as comments on

Lyft 183 Dec 18, 2022
PEP-484 typing stubs for SQLAlchemy 1.4 and SQLAlchemy 2.0

SQLAlchemy 2 Stubs These are PEP-484 typing stubs for SQLAlchemy 1.4 and 2.0. They are released concurrently along with a Mypy extension which is desi

SQLAlchemy 139 Dec 30, 2022
Python classes with types validation at runtime.

typedclasses Python classes with types validation at runtime. (Experimental & Under Development) Installation You can install this library using Pytho

Izhar Ahmad 8 Feb 06, 2022
Flake8 wrapper to make it nice, legacy-friendly, configurable.

THE PROJECT IS ARCHIVED Forks: https://github.com/orsinium/forks It's a Flake8 wrapper to make it cool. Lint md, rst, ipynb, and more. Shareable and r

Life4 232 Dec 16, 2022
Mypy plugin and stubs for SQLAlchemy

Pythonista Stubs Stubs for the Pythonista iOS API. This allows for better error detection and IDE / editor autocomplete. Installation and Usage pip in

Dropbox 521 Dec 29, 2022
Easy saving and switching between multiple KDE configurations.

Konfsave Konfsave is a config manager. That is, it allows you to save, back up, and easily switch between different (per-user) system configurations.

42 Sep 25, 2022
Pyright extension for coc.nvim

coc-pyright Pyright extension for coc.nvim Install :CocInstall coc-pyright Note: Pyright may not work as expected if can't detect project root correct

Heyward Fann 1.1k Jan 02, 2023
docstring style checker

pydocstyle - docstring style checker pydocstyle is a static analysis tool for checking compliance with Python docstring conventions. pydocstyle suppor

Python Code Quality Authority 982 Jan 03, 2023
Custom Python linting through AST expressions

bellybutton bellybutton is a customizable, easy-to-configure linting engine for Python. What is this good for? Tools like pylint and flake8 provide, o

H. Chase Stevens 249 Dec 31, 2022
Tool to automatically fix some issues reported by flake8 (forked from autoflake).

autoflake8 Introduction autoflake8 removes unused imports and unused variables from Python code. It makes use of pyflakes to do this. autoflake8 also

francisco souza 27 Sep 08, 2022
Reference implementation of sentinels for the Python stdlib

Sentinels This is a reference implementation of a utility for the definition of sentinel values in Python. This also includes a draft PEP for the incl

Tal Einat 22 Aug 27, 2022
Collection of awesome Python types, stubs, plugins, and tools to work with them.

Awesome Python Typing Collection of awesome Python types, stubs, plugins, and tools to work with them. Contents Static type checkers Dynamic type chec

TypedDjango 1.2k Jan 04, 2023
Check for python builtins being used as variables or parameters

Flake8 Builtins plugin Check for python builtins being used as variables or parameters. Imagine some code like this: def max_values(list, list2):

Gil Forcada Codinachs 98 Jan 08, 2023